diff --git a/.gitignore b/.gitignore index 11ef85a..325372a 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ yarn-error.log # testing /coverage + +google-services.json diff --git a/README.md b/README.md index 2c46f0c..87982d7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -# Frigate Viewer - -This is mobile application which has been written using React Native to easily browse camera events of Frigate NVR. This is not official app. - -## Android developing - -Follow the instructions of React Native docs to install Android Studio and the emulator. - -Run `npm install` to install dependencies and `npm run android` to start the emulator, compile the app and install it on the emulator or a connected device. - -## iOS developing - -I've never run this application on iOS. It should work in theory, but probably needs some enhancements. +# Frigate Viewer + +This is mobile application which has been written using React Native to easily browse camera events of Frigate NVR. This is not official app. + +## Android developing + +Follow the instructions of React Native docs to install Android Studio and the emulator. + +Run `npm install` to install dependencies and `npm run android` to start the emulator, compile the app and install it on the emulator or a connected device. + +`google-services.json` file should be placed in `./android/app` folder - it should contain credentials to Firebase for Crashlytics service. + +## iOS developing + +I've never run this application on iOS. It should work in theory, but probably needs some enhancements. diff --git a/android/app/build.gradle b/android/app/build.gradle index d0327ad..04a9e96 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,6 +1,8 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" +apply plugin: "com.google.gms.google-services" +apply plugin: "com.google.firebase.crashlytics" /** * This is the configuration block to customize your React Native Android app. diff --git a/android/build.gradle b/android/build.gradle index e2755d3..7fc2347 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,6 +22,8 @@ buildscript { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + classpath("com.google.gms:google-services:4.4.2") + classpath("com.google.firebase:firebase-crashlytics-gradle:3.0.2") } } diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..737d86b --- /dev/null +++ b/firebase.json @@ -0,0 +1,5 @@ +{ + "react-native": { + "crashlytics_debug_enabled": true + } +} \ No newline at end of file diff --git a/helpers/rest.ts b/helpers/rest.ts index 38dded7..0fb29c8 100644 --- a/helpers/rest.ts +++ b/helpers/rest.ts @@ -1,5 +1,6 @@ import {Buffer} from 'buffer'; import {ToastAndroid} from 'react-native'; +import crashlytics from '@react-native-firebase/crashlytics'; import {Credentials} from '../store/settings'; export const authorizationHeader: (credentials: Credentials | null) => { @@ -20,15 +21,21 @@ export const get = async ( json?: boolean, ): Promise => { try { - return await fetch( + crashlytics().log(`GET ${endpoint}`); + const response = await fetch( `${endpoint}${queryParams ? `?${new URLSearchParams(queryParams)}` : ''}`, { headers: { ...authorizationHeader(credentials), }, }, - ).then(res => (json === false ? res.text() : res.json())); + ); + if (response.status === 401) { + throw new Error(`Wrong credentials when tried to reach ${endpoint}`); + } + return response[json === false ? 'text' : 'json'](); } catch (error) { + crashlytics().recordError(error as Error); const e = error as {message: string}; ToastAndroid.show(e.message, ToastAndroid.LONG); return Promise.reject(); @@ -42,7 +49,8 @@ export const post = async ( json?: boolean, ): Promise => { try { - return await fetch( + crashlytics().log(`POST ${endpoint}`); + const response = await fetch( `${endpoint}${queryParams ? `?${new URLSearchParams(queryParams)}` : ''}`, { method: 'post', @@ -50,8 +58,13 @@ export const post = async ( ...authorizationHeader(credentials), }, }, - ).then(res => (json === false ? res.text() : res.json())); + ); + if (response.status === 401) { + throw new Error(`Wrong credentials when tried to reach ${endpoint}`); + } + return response[json === false ? 'text' : 'json'](); } catch (error) { + crashlytics().recordError(error as Error); const e = error as {message: string}; ToastAndroid.show(e.message, ToastAndroid.LONG); return Promise.reject(); @@ -65,7 +78,8 @@ export const del = async ( json?: boolean, ): Promise => { try { - return await fetch( + crashlytics().log(`DELETE ${endpoint}`); + const response = await fetch( `${endpoint}${queryParams ? `?${new URLSearchParams(queryParams)}` : ''}`, { method: 'delete', @@ -73,8 +87,13 @@ export const del = async ( ...authorizationHeader(credentials), }, }, - ).then(res => (json === false ? res.text() : res.json())); + ); + if (response.status === 401) { + throw new Error(`Wrong credentials when tried to reach ${endpoint}`); + } + return response[json === false ? 'text' : 'json'](); } catch (error) { + crashlytics().recordError(error as Error); const e = error as {message: string}; ToastAndroid.show(e.message, ToastAndroid.LONG); return Promise.reject(); diff --git a/i18n/de.ts b/i18n/de.ts index 730b7d9..7d120bd 100644 --- a/i18n/de.ts +++ b/i18n/de.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Protokolle', 'menu.item.settings.label': 'Einstellungen', 'menu.item.author.label': 'Autor', + 'report.topBar.title': 'Problem melden', + 'report.introduction.info': + 'Der Bericht wird einige Protokolle darüber enthalten, wie Sie die Anwendung genutzt haben. Er wird keine Authentifizierungsinformationen enthalten.', + 'report.issue.header': 'Problem', + 'report.issue.description.label': 'Beschreiben Sie das Problem', + 'report.action.send': 'Senden', + 'report.toast.success': 'Das Problem wurde erfolgreich gemeldet', + 'report.error.crash-report-disabled': + 'Das Melden von Abstürzen ist deaktiviert. Gehen Sie zu den Einstellungen und aktivieren Sie es, um ein Problem zu melden. Es wird mir helfen, das Problem besser zu verstehen. Sie können es auch auf GitHub melden.', 'settings.topBar.title': 'Einstellungen', 'settings.error.required': 'Dieses Feld muss ausgefüllt sein.', 'settings.error.min': 'Der Mindestwert ist {min}.', @@ -51,7 +60,7 @@ export default { 'settings.server.protocol.label': 'Protokoll', 'settings.server.host.label': 'Host-Adresse', 'settings.server.port.label': 'Port', - 'settings.server.path.label': 'Pfed', + 'settings.server.path.label': 'Pfad', 'settings.server.username.label': 'Benutzername', 'settings.server.password.label': 'Passwort', 'settings.server.useDemoServerButton': 'Nutzen den Demo Server', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Auto', 'settings.app.colorScheme.option.light': 'Hell', 'settings.app.colorScheme.option.dark': 'Dunkel', + 'settings.app.sendCrashReports.label': 'Absturzberichte senden', 'settings.cameras.header': 'Kameras', 'settings.cameras.imageRefreshFrequency.label': 'Grafik ausgetauscht (sekunden)', diff --git a/i18n/en.ts b/i18n/en.ts index bbf055b..109f27e 100644 --- a/i18n/en.ts +++ b/i18n/en.ts @@ -40,6 +40,16 @@ export default { 'menu.item.logs.label': 'Logs', 'menu.item.settings.label': 'Settings', 'menu.item.author.label': 'Author', + 'menu.item.problem.label': 'Report problem', + 'report.topBar.title': 'Report problem', + 'report.introduction.info': + 'The report will contain some logs of how you used the application. It will not contain your authentication info.', + 'report.issue.header': 'Issue', + 'report.issue.description.label': 'Describe the problem', + 'report.action.send': 'Send', + 'report.toast.success': 'The issue was reported successfully', + 'report.error.crash-report-disabled': + 'Reporting crashes is disabled. Go to settings and enable it to report an issue. It will help me to better understand the matter of the issue. You can also report it on GitHub.', 'settings.topBar.title': 'Settings', 'settings.error.required': 'This field is required.', 'settings.error.min': 'Minimum value is {min}.', @@ -102,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Auto', 'settings.app.colorScheme.option.light': 'Light', 'settings.app.colorScheme.option.dark': 'Dark', + 'settings.app.sendCrashReports.label': 'Send crash reports', 'settings.cameras.header': 'Cameras', 'settings.cameras.imageRefreshFrequency.label': 'Image refresh frequency (seconds)', diff --git a/i18n/es.ts b/i18n/es.ts index f291d22..524552c 100644 --- a/i18n/es.ts +++ b/i18n/es.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Registros', 'menu.item.settings.label': 'Configuración', 'menu.item.author.label': 'Autor', + 'report.topBar.title': 'Reportar problema', + 'report.introduction.info': + 'El informe contendrá algunos registros de cómo utilizaste la aplicación. No contendrá tu información de autenticación.', + 'report.issue.header': 'Problema', + 'report.issue.description.label': 'Describe el problema', + 'report.action.send': 'Enviar', + 'report.toast.success': 'El problema fue reportado con éxito', + 'report.error.crash-report-disabled': + 'La notificación de fallos está deshabilitada. Ve a la configuración y habilítala para reportar un problema. Me ayudará a comprender mejor el problema. También puedes reportarlo en GitHub.', 'settings.topBar.title': 'Configuración', 'settings.error.required': 'Este campo es obligatorio.', 'settings.error.min': 'El valor mínimo es {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Automático', 'settings.app.colorScheme.option.light': 'Claro', 'settings.app.colorScheme.option.dark': 'Oscuro', + 'settings.app.sendCrashReports.label': 'Enviar informes de fallos', 'settings.cameras.header': 'Cámaras', 'settings.cameras.imageRefreshFrequency.label': 'Frecuencia de actualización de la imagen (segundos)', diff --git a/i18n/fr.ts b/i18n/fr.ts index 47eaa57..3726a51 100644 --- a/i18n/fr.ts +++ b/i18n/fr.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Journaux', 'menu.item.settings.label': 'Paramètres', 'menu.item.author.label': 'Auteur', + 'report.topBar.title': 'Signaler un problème', + 'report.introduction.info': + "Le rapport contiendra certains journaux de la manière dont vous avez utilisé l'application. Il ne contiendra pas vos informations d'authentification.", + 'report.issue.header': 'Problème', + 'report.issue.description.label': 'Décrire le problème', + 'report.action.send': 'Envoyer', + 'report.toast.success': 'Le problème a été signalé avec succès', + 'report.error.crash-report-disabled': + "Le rapport d’erreurs est désactivé. Allez dans les paramètres et activez-le pour signaler un problème. Cela m'aidera à mieux comprendre la nature du problème. Vous pouvez également le signaler sur GitHub.", 'settings.topBar.title': 'Paramètres', 'settings.error.required': 'Ce champ est obligatoire.', 'settings.error.min': 'La valeur minimale est {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Auto', 'settings.app.colorScheme.option.light': 'Clair', 'settings.app.colorScheme.option.dark': 'Sombre', + 'settings.app.sendCrashReports.label': 'Envoyer les rapports de plantage', 'settings.cameras.header': 'Caméras', 'settings.cameras.imageRefreshFrequency.label': 'Fréquence d’actualisation des images (secondes)', diff --git a/i18n/it.ts b/i18n/it.ts index 5fd7629..208ee29 100644 --- a/i18n/it.ts +++ b/i18n/it.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Registri', 'menu.item.settings.label': 'Impostazioni', 'menu.item.author.label': 'Autore', + 'report.topBar.title': 'Segnala un problema', + 'report.introduction.info': + "Il rapporto conterrà alcuni registri di come hai utilizzato l'applicazione. Non conterrà le tue informazioni di autenticazione.", + 'report.issue.header': 'Problema', + 'report.issue.description.label': 'Descrivi il problema', + 'report.action.send': 'Invia', + 'report.toast.success': 'Il problema è stato segnalato con successo', + 'report.error.crash-report-disabled': + 'La segnalazione degli arresti anomali è disabilitata. Vai alle impostazioni e abilitalo per segnalare un problema. Mi aiuterà a capire meglio la natura del problema. Puoi anche segnalarlo su GitHub.', 'settings.topBar.title': 'Impostazioni', 'settings.error.required': 'Questo campo è obbligatorio.', 'settings.error.min': 'Il valore minimo è {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Auto', 'settings.app.colorScheme.option.light': 'Chiaro', 'settings.app.colorScheme.option.dark': 'Scuro', + 'settings.app.sendCrashReports.label': 'Invia rapporti sugli arresti anomali', 'settings.cameras.header': 'Telecamere', 'settings.cameras.imageRefreshFrequency.label': "Frequenza di aggiornamento dell'immagine (secondi)", diff --git a/i18n/pl.ts b/i18n/pl.ts index 499348f..29855dc 100644 --- a/i18n/pl.ts +++ b/i18n/pl.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Dzienniki', 'menu.item.settings.label': 'Ustawienia', 'menu.item.author.label': 'Autor', + 'report.topBar.title': 'Zgłoś problem', + 'report.introduction.info': + 'Zgłoszenie będzie zawierało pewne logi dotyczące tego, jak korzystałeś z aplikacji. Nie będzie zawierał informacji uwierzytelniających.', + 'report.issue.header': 'Problem', + 'report.issue.description.label': 'Opisz problem', + 'report.action.send': 'Wyślij', + 'report.toast.success': 'Problem został zgłoszony pomyślnie', + 'report.error.crash-report-disabled': + 'Zgłaszanie awarii jest wyłączone. Przejdź do ustawień i włącz to, aby zgłosić problem. Pomoże mi to lepiej zrozumieć naturę problemu. Możesz również zgłosić to na GitHubie.', 'settings.topBar.title': 'Ustawienia', 'settings.error.required': 'To pole jest wymagane.', 'settings.error.min': 'Minimalną wartością jest {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Automatyczny', 'settings.app.colorScheme.option.light': 'Jasny', 'settings.app.colorScheme.option.dark': 'Ciemny', + 'settings.app.sendCrashReports.label': 'Wysyłaj raporty o awariach', 'settings.cameras.header': 'Kamery', 'settings.cameras.imageRefreshFrequency.label': 'Częstotliwość odświeżania obrazu (sekundy)', diff --git a/i18n/pt.ts b/i18n/pt.ts index 2face06..96e23d0 100644 --- a/i18n/pt.ts +++ b/i18n/pt.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Registros', 'menu.item.settings.label': 'Configurações', 'menu.item.author.label': 'Autor', + 'report.topBar.title': 'Reportar problema', + 'report.introduction.info': + 'O relatório conterá alguns logs de como você utilizou o aplicativo. Ele não conterá suas informações de autenticação.', + 'report.issue.header': 'Problema', + 'report.issue.description.label': 'Descreva o problema', + 'report.action.send': 'Enviar', + 'report.toast.success': 'O problema foi reportado com sucesso', + 'report.error.crash-report-disabled': + 'O relatório de falhas está desativado. Vá para as configurações e ative-o para reportar um problema. Isso me ajudará a entender melhor a questão. Você também pode reportá-lo no GitHub.', 'settings.topBar.title': 'Configurações', 'settings.error.required': 'Este campo é obrigatório.', 'settings.error.min': 'O valor mínimo é {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Auto', 'settings.app.colorScheme.option.light': 'Luz', 'settings.app.colorScheme.option.dark': 'Escuro', + 'settings.app.sendCrashReports.label': 'Enviar relatórios de falhas', 'settings.cameras.header': 'Câmeras', 'settings.cameras.imageRefreshFrequency.label': 'Frequência de atualização da imagem (segundos)', diff --git a/i18n/sv.ts b/i18n/sv.ts index aaac5f4..89806ea 100644 --- a/i18n/sv.ts +++ b/i18n/sv.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Loggar', 'menu.item.settings.label': 'Inställningar', 'menu.item.author.label': 'Författare', + 'report.topBar.title': 'Rapportera problem', + 'report.introduction.info': + 'Rapporten kommer att innehålla vissa loggar om hur du använde applikationen. Den kommer inte att innehålla din autentiseringsinformation.', + 'report.issue.header': 'Problem', + 'report.issue.description.label': 'Beskriv problemet', + 'report.action.send': 'Skicka', + 'report.toast.success': 'Problemet har rapporterats framgångsrikt', + 'report.error.crash-report-disabled': + 'Rapportering av krascher är inaktiverat. Gå till inställningarna och aktivera det för att rapportera ett problem. Det hjälper mig att bättre förstå problemet. Du kan också rapportera det på GitHub.', 'settings.topBar.title': 'Inställningar', 'settings.error.required': 'Det här fältet är obligatoriskt.', 'settings.error.min': 'Minimivärdet är {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Bil', 'settings.app.colorScheme.option.light': 'Ljus', 'settings.app.colorScheme.option.dark': 'Mörk', + 'settings.app.sendCrashReports.label': 'Skicka kraschanmälningar', 'settings.cameras.header': 'Kameror', 'settings.cameras.imageRefreshFrequency.label': 'Uppdateringsfrekvens för bilder (sekunder)', diff --git a/i18n/uk.ts b/i18n/uk.ts index d724319..4fd76ab 100644 --- a/i18n/uk.ts +++ b/i18n/uk.ts @@ -41,6 +41,15 @@ export default { 'menu.item.logs.label': 'Журнали', 'menu.item.settings.label': 'Налаштування', 'menu.item.author.label': 'Автор', + 'report.topBar.title': 'Повідомити про проблему', + 'report.introduction.info': + 'Звіт міститиме деякі журнали про те, як ви використовували додаток. Він не міститиме інформації про вашу автентифікацію.', + 'report.issue.header': 'Проблема', + 'report.issue.description.label': 'Опишіть проблему', + 'report.action.send': 'Відправити', + 'report.toast.success': 'Проблему успішно повідомлено', + 'report.error.crash-report-disabled': + 'Повідомлення про збої вимкнено. Перейдіть до налаштувань і ввімкніть його, щоб повідомити про проблему. Це допоможе мені краще зрозуміти суть проблеми. Ви також можете повідомити про це на GitHub.', 'settings.topBar.title': 'Налаштування', 'settings.error.required': "Це поле обов'язкове.", 'settings.error.min': 'Мінімальне значення до {min}.', @@ -103,6 +112,7 @@ export default { 'settings.app.colorScheme.option.auto': 'Авто', 'settings.app.colorScheme.option.light': 'світло', 'settings.app.colorScheme.option.dark': 'Темний', + 'settings.app.sendCrashReports.label': 'Надсилати звіти про збої', 'settings.cameras.header': 'Камери', 'settings.cameras.imageRefreshFrequency.label': 'Частота оновлення зображення (секунди)', diff --git a/index.js b/index.js index a119637..ce4b179 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ import {Navigation} from 'react-native-navigation'; import {gestureHandlerRootHOC} from 'react-native-gesture-handler'; import {TopBarButton} from './components/icons/TopBarButton'; import {Author} from './views/author/Author'; +import {Report} from './views/report/Report'; import {CameraEventClip} from './views/camera-event-clip/CameraEventClip'; import {CameraPreview} from './views/camera-preview/CameraPreview'; import {CameraEvents} from './views/camera-events/CameraEvents'; @@ -38,6 +39,7 @@ registerComponent('System', System, viewDecorators); registerComponent('Logs', Logs, viewDecorators); registerComponent('Settings', Settings, viewDecorators); registerComponent('Author', Author, viewDecorators); +registerComponent('Report', Report, viewDecorators); registerComponent('Menu', Menu, [ gestureHandlerRootHOC, diff --git a/package-lock.json b/package-lock.json index 386aa5e..053280a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@ant-design/icons-react-native": "^2.3.2", "@lunarr/vlc-player": "^1.0.5", "@react-native-async-storage/async-storage": "2.0.0", + "@react-native-firebase/app": "^21.0.0", + "@react-native-firebase/crashlytics": "^21.0.0", "@reduxjs/toolkit": "^1.9.5", "buffer": "^6.0.3", "date-fns": "^2.30.0", @@ -2179,6 +2181,523 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz", + "integrity": "sha512-CVnHcS4iRJPqtIDc411+UmFldk0ShSK3OB+D0bKD8Ck5Vro6dbK5+APZpkuWpbfdL359DIQUnAaMLE+zs/PVyA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.14.tgz", + "integrity": "sha512-unRVY6SvRqfNFIAA/kwl4vK+lvQAL2HVcgu9zTrUtTyYDmtIt/lOuHJynBMYEgLnKm39YKBDhtqdapP2e++ASw==", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "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==" + }, + "node_modules/@firebase/app": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.11.tgz", + "integrity": "sha512-DuI8c+p/ndPmV6V0i+mcSuaU9mK9Pi9h76WOYFkPNsbmkblEy8bpTOazjG7tnfar6Of1Wn5ohvyOHSRqnN6flQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.8.tgz", + "integrity": "sha512-O49RGF1xj7k6BuhxGpHmqOW5hqBIAEbt2q6POW0lIywx7emYtzPDeQI+ryQpC4zbKX646SoVZ711TN1DBLNSOQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.15.tgz", + "integrity": "sha512-zFIvIFFNqDXpOT2huorz9cwf56VT3oJYRFjSFYdSbGYEJYEaXjLJbfC79lx/zjx4Fh+yuN8pry3TtvwaevrGbg==", + "dependencies": { + "@firebase/app-check": "0.8.8", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "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==" + }, + "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==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.41", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.41.tgz", + "integrity": "sha512-ktJcObWKjlIWq31kXu6sHoqWlhQD5rx0a2F2ZC2JVuEE5A5f7F43VO1Z6lfeRZXMFZbGG/aqIfXqgsP3zD2JYg==", + "dependencies": { + "@firebase/app": "0.10.11", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "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==" + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.14.tgz", + "integrity": "sha512-2eczCSqBl1KUPJacZlFpQayvpilg3dxXLy9cSMTKtQMTQSmondUtPI47P3ikH3bQAXhzKLOE+qVxJ3/IRtu9pw==", + "dependencies": { + "@firebase/auth": "1.7.9", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", + "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "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-compat/node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "optional": true, + "peer": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, + "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==" + }, + "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==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", + "dependencies": { + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", + "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", + "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/database": "1.0.8", + "@firebase/database-types": "1.0.5", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", + "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", + "dependencies": { + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.2.tgz", + "integrity": "sha512-WPkL/DEHuJg1PZPyHn81pNUhitG+7WkpLVdXmoYB23Za3eoM8VzuIn7zcD4Cji6wDCGA6eI1rvGYLtsXmE1OaQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "@firebase/webchannel-wrapper": "1.0.1", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.37", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.37.tgz", + "integrity": "sha512-YwjJePx+m2OGnpKTGFTkcRXQZ+z0+8t7/zuwyOsTmKERobn0kekOv8VAQQmITcC+3du8Ul98O2a0vMH3xwt7jQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/firestore": "4.7.2", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.10.0", + "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==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.8.tgz", + "integrity": "sha512-Lo2rTPDn96naFIlSZKVd1yvRRqqqwiJk7cf9TZhUerwnPKgBzXy+aHE22ry+6EjCaQusUoNai6mU6p+G8QZT1g==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.14.tgz", + "integrity": "sha512-dZ0PKOKQFnOlMfcim39XzaXonSuPPAVuzpqA4ONTIdyaJK/OnBaIEVs/+BH4faa1a2tLeR+Jy15PKqDRQoNIJw==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/functions": "0.11.8", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.10.0", + "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==" + }, + "node_modules/@firebase/installations": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.9.tgz", + "integrity": "sha512-hlT7AwCiKghOX3XizLxXOsTFiFCQnp/oj86zp1UxwDGmyzsyoxtX+UIZyVyH/oBF5+XtblFG9KZzZQ/h+dpy+Q==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.9.tgz", + "integrity": "sha512-2lfdc6kPXR7WaL4FCQSQUhXcPbI7ol3wF+vkgtU25r77OxPf8F/VmswQ7sgIkBBWtymn5ZF20TIKtnOj9rjb6w==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.10.0", + "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==", + "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==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.11", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.11.tgz", + "integrity": "sha512-zn5zGhF46BmiZ7W9yAUoHlqzJGakmWn1FNp//roXHN62dgdEFIKfXY7IODA2iQiXpmUO3sBdI/Tf+Hsft1mVkw==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.11.tgz", + "integrity": "sha512-2NCkfE1L9jSn5OC+2n5rGAz5BEAQreK2lQGdPYQEJlAbKB2efoF+2FdiQ+LD8SlioSXz66REfeaEdesoLPFQcw==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/messaging": "0.12.11", + "@firebase/util": "1.10.0", + "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==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.9.tgz", + "integrity": "sha512-PnVaak5sqfz5ivhua+HserxTJHtCar/7zM0flCX6NkzBNzJzyzlH4Hs94h2Il0LQB99roBqoE5QT1JqWqcLJHQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.9.tgz", + "integrity": "sha512-dNl95IUnpsu3fAfYBZDCVhXNkASE0uo4HYaEPd2/PKscfTvsgqFAOxfAXzBEDOnynDWiaGUnb5M1O00JQ+3FXA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.9", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.10.0", + "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==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.9.tgz", + "integrity": "sha512-EO1NLCWSPMHdDSRGwZ73kxEEcTopAxX1naqLJFNApp4hO8WfKfmEpmjxmP5TrrnypjIf2tUkYaKsfbEA7+AMmA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.9.tgz", + "integrity": "sha512-AxzGpWfWFYejH2twxfdOJt5Cfh/ATHONegTd/a0p5flEzsD5JsxXgfkFToop+mypEL3gNwawxrxlZddmDoNxyA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.10.0", + "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==" + }, + "node_modules/@firebase/storage": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.2.tgz", + "integrity": "sha512-fxuJnHshbhVwuJ4FuISLu+/76Aby2sh+44ztjF2ppoe0TELIDxPW6/r1KGlWYt//AD0IodDYYA8ZTN89q8YqUw==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.12.tgz", + "integrity": "sha512-hA4VWKyGU5bWOll+uwzzhEMMYGu9PlKQc1w4DWxB3aIErWYzonrZjF0icqNQZbwKNIdh8SHjZlFeB2w6OSsjfg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.10.0", + "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==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.4.tgz", + "integrity": "sha512-EBSqyu9eg8frQlVU9/HjKtHN7odqbh9MtAcVz3WwHj4gLCLOoN9F/o+oxlq3CxvFrd3CNTZwu6d2mZtVlEInng==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "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==" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", @@ -2265,6 +2784,35 @@ "tslib": "^2.4.0" } }, + "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==", + "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==", + "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/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3111,6 +3659,60 @@ "node": ">= 8" } }, + "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==" + }, + "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==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "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==", + "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==" + }, + "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==" + }, + "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==" + }, + "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==" + }, + "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==" + }, "node_modules/@react-native-async-storage/async-storage": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.0.0.tgz", @@ -4027,6 +4629,42 @@ "node": ">=8" } }, + "node_modules/@react-native-firebase/app": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-21.0.0.tgz", + "integrity": "sha512-CR7KMAWb5uKYyjvWlFdMYrchNF+8XXOk+hnDYcIe+jsHTu+L2tCBzmgE9U0RMUEOdJBIPQotAdnenVdipEbbUg==", + "dependencies": { + "firebase": "10.13.2", + "superstruct": "^0.6.2" + }, + "peerDependencies": { + "expo": ">=47.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@react-native-firebase/crashlytics": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/crashlytics/-/crashlytics-21.0.0.tgz", + "integrity": "sha512-cAAC7MilbKQdIXjr40ZX/iDv0Gqrb9U0axUfmqIEFrnIyuwszqZVLsDOHYjvUTxJWi3M3KwCGPTd0UYhYvAa9g==", + "dependencies": { + "stacktrace-js": "^2.0.2" + }, + "peerDependencies": { + "@react-native-firebase/app": "21.0.0", + "expo": ">=47.0.0" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, "node_modules/@react-native/assets-registry": { "version": "0.73.1", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", @@ -7790,6 +8428,17 @@ "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==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -7986,6 +8635,74 @@ "node": ">=8" } }, + "node_modules/firebase": { + "version": "10.13.2", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.13.2.tgz", + "integrity": "sha512-YeI+TO5rJsoyZsVFx9WiN5ibdVCIigYTWwldRTMfCzrSPrJFVGao4acYj3x0EYGKDIgSgEyVBayD5BffD4Eyow==", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-compat": "0.2.14", + "@firebase/app": "0.10.11", + "@firebase/app-check": "0.8.8", + "@firebase/app-check-compat": "0.3.15", + "@firebase/app-compat": "0.2.41", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.9", + "@firebase/auth-compat": "0.5.14", + "@firebase/database": "1.0.8", + "@firebase/database-compat": "1.0.8", + "@firebase/firestore": "4.7.2", + "@firebase/firestore-compat": "0.3.37", + "@firebase/functions": "0.11.8", + "@firebase/functions-compat": "0.3.14", + "@firebase/installations": "0.6.9", + "@firebase/installations-compat": "0.2.9", + "@firebase/messaging": "0.12.11", + "@firebase/messaging-compat": "0.2.11", + "@firebase/performance": "0.6.9", + "@firebase/performance-compat": "0.2.9", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-compat": "0.2.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-compat": "0.3.12", + "@firebase/util": "1.10.0", + "@firebase/vertexai-preview": "0.0.4" + } + }, + "node_modules/firebase/node_modules/@firebase/auth": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", + "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "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/node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "optional": true, + "peer": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -8028,6 +8745,25 @@ "is-callable": "^1.1.3" } }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/formik": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", @@ -8489,6 +9225,11 @@ "node": ">= 0.8" } }, + "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==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -8497,6 +9238,11 @@ "node": ">=10.17.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -8790,6 +9536,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -11069,6 +11823,11 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "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==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -11294,6 +12053,11 @@ "node": ">=6" } }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -11864,6 +12628,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -12634,6 +13418,29 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "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/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -13904,6 +14711,14 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -13928,6 +14743,33 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/stacktrace-parser": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", @@ -14192,6 +15034,50 @@ "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" }, + "node_modules/superstruct": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.6.2.tgz", + "integrity": "sha512-lvA97MFAJng3rfjcafT/zGTSWm6Tbpk++DP6It4Qg7oNaeM+2tdJMuVgGje21/bIpBEs6iQql1PJH6dKTjl4Ig==", + "dependencies": { + "clone-deep": "^2.0.1", + "kind-of": "^6.0.1" + } + }, + "node_modules/superstruct/node_modules/clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dependencies": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstruct/node_modules/shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstruct/node_modules/shallow-clone/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -14586,6 +15472,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -14792,6 +15686,27 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "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==", + "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==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", @@ -15236,4 +16151,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 32e4262..96d28c7 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "android": "react-native run-android", + "android:release": "react-native run-android --mode=release", "android:build": "react-native build-android --mode=release", "android:bundle": "cd android && ./gradlew bundleRelease", "ios": "react-native run-ios", @@ -16,6 +17,8 @@ "@ant-design/icons-react-native": "^2.3.2", "@lunarr/vlc-player": "^1.0.5", "@react-native-async-storage/async-storage": "2.0.0", + "@react-native-firebase/app": "^21.0.0", + "@react-native-firebase/crashlytics": "^21.0.0", "@reduxjs/toolkit": "^1.9.5", "buffer": "^6.0.3", "date-fns": "^2.30.0", diff --git a/store/settings.ts b/store/settings.ts index bb03941..abf8693 100644 --- a/store/settings.ts +++ b/store/settings.ts @@ -65,6 +65,7 @@ export interface ISettings { }; app: { colorScheme: 'auto' | 'light' | 'dark'; + sendCrashReports: boolean; }; cameras: { refreshFrequency: number; @@ -94,6 +95,7 @@ export const initialSettings: ISettings = { }, app: { colorScheme: 'auto', + sendCrashReports: true, }, locale: { region: NativeModules.I18nManager.localeIdentifier, @@ -213,11 +215,16 @@ export const selectLocaleRegion = (state: RootState) => export const selectLocaleDatesDisplay = (state: RootState) => selectLocale(state).datesDisplay; +/* app */ + export const selectApp = (state: RootState) => selectSettings(state).app; export const selectAppColorScheme = (state: RootState) => selectApp(state)?.colorScheme || 'auto'; +export const selectAppSendCrashReports = (state: RootState) => + selectApp(state).sendCrashReports; + /* cameras */ export const selectCameras = (state: RootState) => diff --git a/views/camera-event-clip/CameraEventClip.tsx b/views/camera-event-clip/CameraEventClip.tsx index f6b317c..596b14f 100644 --- a/views/camera-event-clip/CameraEventClip.tsx +++ b/views/camera-event-clip/CameraEventClip.tsx @@ -10,6 +10,7 @@ import {ActivityIndicator, Text, View} from 'react-native'; import {NavigationFunctionComponent} from 'react-native-navigation'; import VLCPlayer, {State} from '@lunarr/vlc-player'; import RNFetchBlob from 'rn-fetch-blob'; +import crashlytics from '@react-native-firebase/crashlytics'; import {ZoomableView} from '../../components/ZoomableView'; import { selectServerApiUrl, @@ -146,6 +147,7 @@ const VideoPlayer: FC = ({ setUri(filePath); setLoading(false); } catch (err) { + crashlytics().recordError(err as Error); setLoading(false); setError(JSON.stringify(err)); } diff --git a/views/camera-events/CameraEvent.tsx b/views/camera-events/CameraEvent.tsx index e172eb1..f1d0906 100644 --- a/views/camera-events/CameraEvent.tsx +++ b/views/camera-events/CameraEvent.tsx @@ -2,6 +2,7 @@ import React, {FC, useCallback, useEffect, useMemo, useState} from 'react'; import {useIntl} from 'react-intl'; import {Image, TouchableWithoutFeedback, View} from 'react-native'; import {Colors, Drawer, DrawerItemProps} from 'react-native-ui-lib'; +import crashlytics from '@react-native-firebase/crashlytics'; import {del, post} from '../../helpers/rest'; import { selectServerApiUrl, @@ -84,10 +85,13 @@ export const CameraEvent: FC = props => { async (snapshot: string) => { if (snapshot) { try { + crashlytics().log(`Get image size of ${snapshot}`); Image.getSize(snapshot, (width, height) => { onSnapshotDimensions(width, height); }); - } catch (err) {} + } catch (err) { + crashlytics().recordError(err as Error); + } } }, [numColumns, snapshotHeight, onSnapshotDimensions], diff --git a/views/camera-events/Share.tsx b/views/camera-events/Share.tsx index 752aceb..722199c 100644 --- a/views/camera-events/Share.tsx +++ b/views/camera-events/Share.tsx @@ -12,6 +12,7 @@ import { selectServerCredentials, } from '../../store/settings'; import {ActivityIndicator, Text, ToastAndroid} from 'react-native'; +import crashlytics from '@react-native-firebase/crashlytics'; import {clipFilename, snapshotFilename} from './eventHelpers'; import {useStyles} from '../../helpers/colors'; @@ -67,6 +68,7 @@ export const Share: FC = ({event, onDismiss}) => { const download = async (filename: string, url: string) => { try { + crashlytics().log(`Share ${filename} from ${url}`); setLoading(true); const dirs = RNFetchBlob.fs.dirs; const filePath = `${dirs.CacheDir}/${filename}`; @@ -84,6 +86,7 @@ export const Share: FC = ({event, onDismiss}) => { setLoading(false); return filePath; } catch (err) { + crashlytics().recordError(err as Error); setLoading(false); ToastAndroid.show(JSON.stringify(err), ToastAndroid.LONG); } diff --git a/views/cameras-list/CameraTile.tsx b/views/cameras-list/CameraTile.tsx index cb19b61..76a69cd 100644 --- a/views/cameras-list/CameraTile.tsx +++ b/views/cameras-list/CameraTile.tsx @@ -1,15 +1,9 @@ -import React, { - FC, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, {FC, useEffect, useMemo, useRef, useState} from 'react'; import type {PropsWithChildren} from 'react'; import {Dimensions, Image, StyleSheet, Text, View} from 'react-native'; import {Navigation} from 'react-native-navigation'; import {Carousel} from 'react-native-ui-lib'; +import crashlytics from '@react-native-firebase/crashlytics'; import {useAppDispatch, useAppSelector} from '../../store/store'; import { selectCamerasactionWhenPressed, @@ -153,6 +147,7 @@ export const CameraTile: FC = ({cameraName, componentId}) => { } if (lastImageSrc) { try { + crashlytics().log(`Get camera preview size from ${lastImageSrc}`); Image.getSize(lastImageSrc, (width, height) => { const proportion = height / width; const windowWidth = Dimensions.get('window').width; @@ -162,7 +157,9 @@ export const CameraTile: FC = ({cameraName, componentId}) => { dispatch(setCameraPreviewHeight(newHeight)); } }); - } catch (err) {} + } catch (err) { + crashlytics().recordError(err as Error); + } } }; diff --git a/views/cameras-list/CamerasList.tsx b/views/cameras-list/CamerasList.tsx index 55e3d62..bf7884b 100644 --- a/views/cameras-list/CamerasList.tsx +++ b/views/cameras-list/CamerasList.tsx @@ -19,16 +19,9 @@ import {useAppDispatch, useAppSelector} from '../../store/store'; import {menuButton, useMenu} from '../menu/menuHelpers'; import {CameraTile} from './CameraTile'; import {messages} from './messages'; -import { useNoServer } from '../settings/useNoServer'; -import { Background } from '../../components/Background'; - -const styles = StyleSheet.create({ - noCameras: { - padding: 20, - color: 'black', - textAlign: 'center', - }, -}); +import {useNoServer} from '../settings/useNoServer'; +import {Background} from '../../components/Background'; +import {useStyles} from '../../helpers/colors'; interface IConfigResponse { cameras: Record< @@ -43,6 +36,14 @@ interface IConfigResponse { } export const CamerasList: NavigationFunctionComponent = ({componentId}) => { + const styles = useStyles(({theme}) => ({ + noCameras: { + padding: 20, + color: theme.text, + textAlign: 'center', + }, + })); + useMenu(componentId, 'camerasList'); useNoServer(); const [loading, setLoading] = useState(true); diff --git a/views/menu/Menu.tsx b/views/menu/Menu.tsx index 0c60102..208daf6 100644 --- a/views/menu/Menu.tsx +++ b/views/menu/Menu.tsx @@ -80,6 +80,12 @@ export const authorMenuItem: IMenuItem = { view: 'Author', }; +export const reportProblemMenuItem: IMenuItem = { + id: 'report', + icon: 'send', + view: 'Report', +}; + export const navigateToMenuItem = ({view, modal, passProps}: IMenuItem) => () => { @@ -156,6 +162,7 @@ export const Menu: FC = ({current}) => { systemMenuItem, logsMenuItem, settingsMenuItem, + reportProblemMenuItem, authorMenuItem, ].map(item => ({ ...item, diff --git a/views/menu/menuHelpers.ts b/views/menu/menuHelpers.ts index 627aa9b..7f70b07 100644 --- a/views/menu/menuHelpers.ts +++ b/views/menu/menuHelpers.ts @@ -9,7 +9,8 @@ export type MenuId = | 'system' | 'logs' | 'settings' - | 'author'; + | 'author' + | 'report'; export const useSelectedMenuItem = (current?: MenuId) => { useEffect(() => { diff --git a/views/menu/messages.ts b/views/menu/messages.ts index 4fd93ae..a54ad45 100644 --- a/views/menu/messages.ts +++ b/views/menu/messages.ts @@ -10,6 +10,7 @@ export const messages = makeMessages('menu', { 'item.logs.label': 'Logs', 'item.settings.label': 'Settings', 'item.author.label': 'Author', + 'item.report.label': 'Report problem', }); export type MessageKey = typeof messages extends Record< diff --git a/views/report/Report.tsx b/views/report/Report.tsx new file mode 100644 index 0000000..075eeec --- /dev/null +++ b/views/report/Report.tsx @@ -0,0 +1,154 @@ +import {Formik, FormikProps} from 'formik'; +import React, {useEffect, useMemo, useRef} from 'react'; +import {useIntl} from 'react-intl'; +import {Keyboard, Text, ToastAndroid} from 'react-native'; +import {Navigation, NavigationFunctionComponent} from 'react-native-navigation'; +import crashlytics from '@react-native-firebase/crashlytics'; +import {Input} from '../../components/forms/Input'; +import {Label} from '../../components/forms/Label'; +import {Section} from '../../components/forms/Section'; +import {messages} from './messages'; +import {ActionBar, View} from 'react-native-ui-lib'; +import {ScrollView} from 'react-native-gesture-handler'; +import {useTheme, useStyles} from '../../helpers/colors'; +import {useAppSelector} from '../../store/store'; +import {selectAppSendCrashReports, selectSettings} from '../../store/settings'; +import {menuButton, useMenu} from '../menu/menuHelpers'; + +interface Problem { + issue: { + description: string; + }; +} + +const initialValues = { + issue: { + description: '', + }, +}; + +export const Report: NavigationFunctionComponent = ({componentId}) => { + const styles = useStyles(({theme}) => ({ + wrapper: { + flex: 1, + justifyContent: 'space-between', + }, + scrollArea: { + paddingVertical: 8, + paddingHorizontal: 16, + width: '100%', + flexGrow: 1, + backgroundColor: theme.background, + }, + p: { + color: theme.text, + }, + demoServerButton: { + color: theme.link, + }, + tip: { + color: theme.text, + }, + })); + const theme = useTheme(); + + const formRef = useRef>(null); + const currentSettings = useAppSelector(selectSettings); + const sendCrashReportEnabled = useAppSelector(selectAppSendCrashReports); + const intl = useIntl(); + + useMenu(componentId, 'report'); + + useEffect(() => { + Navigation.mergeOptions(componentId, { + topBar: { + title: { + text: intl.formatMessage(messages['topBar.title']), + }, + leftButtons: [menuButton], + }, + }); + }, [componentId, intl]); + + const send = async (problem: Problem) => { + Keyboard.dismiss(); + crashlytics().log('Reporting an issue'); + if (problem.issue.description) { + crashlytics().log(`Description: ${problem.issue.description}`); + } + const {server, ...settingsWithoutServerData} = currentSettings; + const settings = ( + Object.keys( + settingsWithoutServerData, + ) as (keyof typeof settingsWithoutServerData)[] + ).reduce( + (obj, key) => ({ + ...obj, + [key]: JSON.stringify(settingsWithoutServerData[key]), + }), + {}, + ); + await crashlytics().setAttributes(settings); + crashlytics().recordError(new Error('Reported by user'), 'reported'); + formRef.current?.resetForm(); + crashlytics().crash(); + ToastAndroid.show( + intl.formatMessage(messages['toast.success']), + ToastAndroid.LONG, + ); + }; + + const actions = useMemo(() => { + const sendButton = { + label: intl.formatMessage(messages['action.send']), + color: theme.link, + onPress: () => { + formRef.current?.handleSubmit(); + }, + }; + return [sendButton]; + }, [formRef, theme]); + + if (!sendCrashReportEnabled) { + return ( + + + {intl.formatMessage(messages['error.crash-report-disabled'])} + + + ); + } + + return ( + + {({values, handleBlur, handleChange, errors, touched}) => ( + + + + {intl.formatMessage(messages['introduction.info'])} + +
+ +
+
+ +
+ )} +
+ ); +}; diff --git a/views/report/messages.ts b/views/report/messages.ts new file mode 100644 index 0000000..48d4699 --- /dev/null +++ b/views/report/messages.ts @@ -0,0 +1,21 @@ +import {MessageDescriptor} from 'react-intl'; +import {makeMessages} from '../../helpers/locale'; + +export const messages = makeMessages('report', { + 'topBar.title': 'Report problem', + 'introduction.info': + 'The report will contain some logs of how you used the application. It will not contain your authentication info.', + 'issue.header': 'Issue', + 'issue.description.label': 'Describe the problem', + 'action.send': 'Send', + 'toast.success': 'The issue was reported successfully', + 'error.crash-report-disabled': + 'Reporting crashes is disabled. Go to settings and enable it to report an issue. It will help me to better understand the matter of the issue. You can also report it on GitHub.', +}); + +export type MessageKey = typeof messages extends Record< + infer R, + MessageDescriptor +> + ? R + : never; diff --git a/views/settings/Settings.tsx b/views/settings/Settings.tsx index a2afe05..434985a 100644 --- a/views/settings/Settings.tsx +++ b/views/settings/Settings.tsx @@ -4,6 +4,7 @@ import {useIntl} from 'react-intl'; import {Keyboard, Pressable, Text} from 'react-native'; import {Navigation, NavigationFunctionComponent} from 'react-native-navigation'; import * as yup from 'yup'; +import crashlytics from '@react-native-firebase/crashlytics'; import {Dropdown} from '../../components/forms/Dropdown'; import {Input} from '../../components/forms/Input'; import {Label} from '../../components/forms/Label'; @@ -99,6 +100,9 @@ export const Settings: NavigationFunctionComponent = () => { const save = useCallback( (settings: ISettings) => { + crashlytics().setCrashlyticsCollectionEnabled( + settings.app.sendCrashReports, + ); dispatch(saveSettings(settings)); Keyboard.dismiss(); Navigation.dismissAllModals(); @@ -332,6 +336,17 @@ export const Settings: NavigationFunctionComponent = () => { onValueChange={handleChange('app.colorScheme')} /> +