From 92190bf4cc8752bebd39d81db0d67f67c391628a Mon Sep 17 00:00:00 2001 From: Andrew Corbin Date: Mon, 30 Jun 2025 15:21:40 -0500 Subject: [PATCH 01/26] Add Google Sign-In login screen --- README.md | 1 + app.config.js | 2 ++ app/_layout.tsx | 16 +++++++++++++--- app/login.tsx | 46 ++++++++++++++++++++++++++++++++++++++++++++++ hooks/useAuth.ts | 34 ++++++++++++++++++++++++++++++++++ package-lock.json | 17 +++++++++++++++++ package.json | 1 + 7 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 app/login.tsx create mode 100644 hooks/useAuth.ts diff --git a/README.md b/README.md index 67da738..04669ae 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ CodeBuilder Admin is a mobile application built with React Native and Expo. This ```env GOOGLE_MAPS_API_KEY=your_google_maps_api_key + GOOGLE_WEB_CLIENT_ID=your_google_web_client_id FIREBASE_API_KEY=your_firebase_api_key FIREBASE_AUTH_DOMAIN=your_firebase_auth_domain FIREBASE_PROJECT_ID=your_firebase_project_id diff --git a/app.config.js b/app.config.js index 97e869a..eb3d6b2 100644 --- a/app.config.js +++ b/app.config.js @@ -42,6 +42,7 @@ module.exports = { firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET, firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, firebaseAppId: process.env.FIREBASE_APP_ID, + googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID, }, orientation: "portrait", icon: "./assets/images/icon.png", @@ -135,6 +136,7 @@ module.exports = { ], "@react-native-firebase/app", "@react-native-firebase/messaging", + "@react-native-google-signin/google-signin", // Add the iOS sound plugin [withIOSSounds], [ diff --git a/app/_layout.tsx b/app/_layout.tsx index 64d061e..fbac8d1 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useNavigationContainerRef } from "@react-navigation/native"; import { useReactNavigationDevTools } from "@dev-plugins/react-navigation"; import { SplashScreen } from "expo-router"; @@ -18,6 +18,7 @@ import "react-native-reanimated"; import { useColorScheme } from "@/hooks/useColorScheme"; import { registerBackgroundFetch } from "@/utils/tasks.utils"; import ErrorBoundary from "@/components/ErrorBoundary"; +import { AuthProvider, useAuth } from "@/hooks/useAuth"; import { setJSExceptionHandler, @@ -100,17 +101,26 @@ export default function RootLayout() { return null; } - return ; + return ( + + + + ); } function RootLayoutNav() { const colorScheme = useColorScheme(); + const { user } = useAuth(); return ( - + {user ? ( + + ) : ( + + )} diff --git a/app/login.tsx b/app/login.tsx new file mode 100644 index 0000000..6e89e19 --- /dev/null +++ b/app/login.tsx @@ -0,0 +1,46 @@ +import { useEffect } from 'react'; +import { View, StyleSheet } from 'react-native'; +import { GoogleSignin, GoogleSigninButton } from '@react-native-google-signin/google-signin'; +import Constants from 'expo-constants'; +import { useAuth } from '@/hooks/useAuth'; + +export default function LoginScreen() { + const { setUser } = useAuth(); + + useEffect(() => { + GoogleSignin.configure({ + webClientId: Constants.expoConfig?.extra?.googleWebClientId, + }); + }, []); + + const signIn = async () => { + try { + await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true }); + const result = await GoogleSignin.signIn(); + if (result.type === 'success') { + setUser({ idToken: result.data.idToken ?? '', user: result.data.user }); + fetch('https://new.codebuilder.org/api/auth/google', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ idToken: result.data.idToken }), + }).catch((e) => console.error('Auth callback error:', e)); + } + } catch (e) { + console.error('Google sign in error:', e); + } + }; + + return ( + + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, +}); diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts new file mode 100644 index 0000000..a6bf930 --- /dev/null +++ b/hooks/useAuth.ts @@ -0,0 +1,34 @@ +import { createContext, useContext, useState, ReactNode } from 'react'; + +export interface AuthUser { + idToken: string; + user: { + id: string; + name: string | null; + email: string; + photo: string | null; + familyName: string | null; + givenName: string | null; + }; +} + +interface AuthContextType { + user: AuthUser | null; + setUser: (user: AuthUser | null) => void; +} + +const AuthContext = createContext({ + user: null, + setUser: () => {}, +}); + +export const AuthProvider = ({ children }: { children: ReactNode }) => { + const [user, setUser] = useState(null); + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); diff --git a/package-lock.json b/package-lock.json index 1e46666..bbd430d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@react-native-firebase/app": "^22.2.1", "@react-native-firebase/auth": "^22.2.1", "@react-native-firebase/messaging": "^22.2.1", + "@react-native-google-signin/google-signin": "^15.0.0", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "babel-preset-expo": "~13.0.0", @@ -5488,6 +5489,22 @@ } } }, + "node_modules/@react-native-google-signin/google-signin": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-15.0.0.tgz", + "integrity": "sha512-oU49nE+Z9TT/WaO1K7BH/QL2Nx3d2T3I5PGcYdD8swKNtfhMt8qCX/3mOnNEzthTBPrR0CWFGo3LXpkYspalog==", + "license": "MIT", + "peerDependencies": { + "expo": ">=52.0.40", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.4", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.4.tgz", diff --git a/package.json b/package.json index 526a237..7ff2008 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@react-native-firebase/app": "^22.2.1", "@react-native-firebase/auth": "^22.2.1", "@react-native-firebase/messaging": "^22.2.1", + "@react-native-google-signin/google-signin": "^15.0.0", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "babel-preset-expo": "~13.0.0", From c36cbe9c313defed4ac913ffb2cfa3516b68419d Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:12:56 -0500 Subject: [PATCH 02/26] Updated workflow to run on any branch & to create pre-release builds on anything not main. --- .github/workflows/eas-android-build.yml | 45 ++++++++++++++++--------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/.github/workflows/eas-android-build.yml b/.github/workflows/eas-android-build.yml index f52813d..3dad552 100644 --- a/.github/workflows/eas-android-build.yml +++ b/.github/workflows/eas-android-build.yml @@ -4,7 +4,7 @@ permissions: on: push: - branches: [main] + branches: ['**'] # run on any branch workflow_dispatch: env: @@ -21,6 +21,8 @@ jobs: build_number: ${{ steps.version-control.outputs.build_number }} build_date: ${{ steps.version-control.outputs.build_date }} is_production: ${{ steps.version-control.outputs.is_production }} + eas_profile: ${{ steps.version-control.outputs.eas_profile }} # added dynamic profile output + eas_channel: ${{ steps.version-control.outputs.eas_channel }} # added dynamic channel output steps: # ======================== # πŸ› οΈ Repository Setup @@ -68,13 +70,18 @@ jobs: - name: "🏷️ Set CI/CD Versions" id: version-control run: | - # Use version from version.json (requires jq) - if [ "${{ github.ref }}" == "refs/heads/main" ]; then + # Determine branch and environment + BRANCH_NAME=${GITHUB_REF#refs/heads/} + if [ "${BRANCH_NAME}" == "main" ]; then APP_VERSION=$(jq -r '.version' version.json) IS_PRODUCTION="true" + EAS_PROFILE=production + EAS_CHANNEL=production else - APP_VERSION="1.0.0-prerelease.${{ github.run_number }}" + APP_VERSION="1.0.0-prerelease.${GITHUB_RUN_NUMBER}" IS_PRODUCTION="false" + EAS_PROFILE=development # dev build profile + EAS_CHANNEL="dev-${BRANCH_NAME}" # dev release channel fi # Generate build identifiers @@ -82,15 +89,19 @@ jobs: BUILD_DATE=$(date +'%Y%m%d-%H%M%S') # Set outputs for downstream jobs - echo "app_version=$APP_VERSION" >> $GITHUB_OUTPUT - echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT - echo "build_date=$BUILD_DATE" >> $GITHUB_OUTPUT - echo "is_production=$IS_PRODUCTION" >> $GITHUB_OUTPUT + echo "app_version=${APP_VERSION}" >> $GITHUB_OUTPUT + echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT + echo "build_date=${BUILD_DATE}" >> $GITHUB_OUTPUT + echo "is_production=${IS_PRODUCTION}" >> $GITHUB_OUTPUT + echo "eas_profile=${EAS_PROFILE}" >> $GITHUB_OUTPUT + echo "eas_channel=${EAS_CHANNEL}" >> $GITHUB_OUTPUT # Export environment variables - echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV - echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV - echo "BUILD_DATE=$BUILD_DATE" >> $GITHUB_ENV + echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_ENV + echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV + echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV + echo "EAS_PROFILE=${EAS_PROFILE}" >> $GITHUB_ENV + echo "EAS_CHANNEL=${EAS_CHANNEL}" >> $GITHUB_ENV # ======================== # πŸ” EAS Setup & Auth @@ -107,9 +118,9 @@ jobs: - name: "πŸš€ Trigger EAS Build" id: build run: | - echo "πŸ”„ Initializing build process..." + echo "πŸ”„ Initializing build process with profile=${EAS_PROFILE} on channel=${EAS_CHANNEL}..." sudo apt-get install -y jq - BUILD_JSON=$(npx eas build -p android --profile production --non-interactive --json) + BUILD_JSON=$(npx eas build -p android --profile $EAS_PROFILE --channel $EAS_CHANNEL --non-interactive --json) echo "Raw build output: $BUILD_JSON" BUILD_ID=$(echo "$BUILD_JSON" | jq -r '.[0].id') if [[ -z "$BUILD_ID" || "$BUILD_ID" == "null" ]]; then @@ -170,7 +181,8 @@ jobs: SLEEP_TIME=30 while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do - echo -e "\n=== Attempt $((RETRY_COUNT+1))/$MAX_RETRIES ===" + echo -e " +=== Attempt $((RETRY_COUNT+1))/$MAX_RETRIES ===" # Fetch build status in JSON format BUILD_STATUS_JSON=$(npx eas build:view --json $BUILD_ID) @@ -233,7 +245,6 @@ jobs: id: download run: | echo "πŸ”½ Retrieving APK URL..." - # Use the build:view command to get a clean JSON response APK_URL=$(npx eas build:view --json ${{ needs.build-android.outputs.build_id }} | jq -r '.artifacts.buildUrl') if [[ -z "$APK_URL" || "$APK_URL" == "null" ]]; then echo "❌ Error: No APK URL found!" @@ -321,6 +332,8 @@ jobs: RELEASE_TITLE="Nightly Build (${{ needs.build-android.outputs.build_date }})" fi echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_OUTPUT + echo "RELEASE_TITLE=${RELEASE_TITLE}" >> $GITHUB_OUTPUT + - name: "πŸŽ‰ Publish GitHub Release" uses: softprops/action-gh-release@v2 with: @@ -328,4 +341,4 @@ jobs: name: ${{ steps.release-type.outputs.RELEASE_TITLE }} body_path: changelog.txt files: app-release.apk - prerelease: ${{ needs.build-android.outputs.is_production != 'true' }} + prerelease: ${{ needs.build-android.outputs.is_production != 'true' }} \ No newline at end of file From db9890418c09382a78ab2687e68db0c4cfa697c4 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:13:13 -0500 Subject: [PATCH 03/26] Added types to color props. --- app/(tabs)/_layout.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 9c88bd2..96ff612 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -29,7 +29,9 @@ export default function TabLayout() { name="index" options={{ title: "Home", - tabBarIcon: ({ color }) => , + tabBarIcon: ({ color }: { color: string }) => ( + + ), headerRight: () => ( @@ -50,7 +52,7 @@ export default function TabLayout() { name="jobs" options={{ title: "Jobs", - tabBarIcon: ({ color }) => ( + tabBarIcon: ({ color }: { color: string }) => ( ), }} @@ -59,14 +61,18 @@ export default function TabLayout() { name="settings" options={{ title: "Settings", - tabBarIcon: ({ color }) => , + tabBarIcon: ({ color }: { color: string }) => ( + + ), }} /> , + tabBarIcon: ({ color }: { color: string }) => ( + + ), }} /> From 2d56de73d9f1d4ecf6662a8641b0adaa2326c27e Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:13:27 -0500 Subject: [PATCH 04/26] Instantiated globalErrorHandler. --- app/_layout.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/_layout.tsx b/app/_layout.tsx index 64d061e..f52beb2 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -25,6 +25,7 @@ import { } from "react-native-exception-handler"; import { reportError, safeReport } from "@/services/errorReporting.service"; import { showFatalErrorNotification } from "@/utils/notifications.utils"; +import { setupGlobalErrorHandlers } from "../utils/globalErrorhandler"; // --- Global Exception Handler Setup --- @@ -106,6 +107,10 @@ export default function RootLayout() { function RootLayoutNav() { const colorScheme = useColorScheme(); + useEffect(() => { + setupGlobalErrorHandlers(); + }, []); + return ( From 059f67e8dae0a7abea232505f9953aa0051e824e Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:14:30 -0500 Subject: [PATCH 05/26] Created global build script & new setupErrorExceptionHandler script. --- scripts/build-config.json | 7 + scripts/buildScripts.js | 80 ++++++++++++ scripts/setupExceptionHandler.js | 211 +++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 scripts/build-config.json create mode 100644 scripts/buildScripts.js create mode 100644 scripts/setupExceptionHandler.js diff --git a/scripts/build-config.json b/scripts/build-config.json new file mode 100644 index 0000000..01ea4be --- /dev/null +++ b/scripts/build-config.json @@ -0,0 +1,7 @@ +{ + "scripts": [ + "setupBatteryOptimizations.js", + "notificationSounds.ts", + "setupExceptionHandler.js" + ] +} \ No newline at end of file diff --git a/scripts/buildScripts.js b/scripts/buildScripts.js new file mode 100644 index 0000000..24de849 --- /dev/null +++ b/scripts/buildScripts.js @@ -0,0 +1,80 @@ +const fs = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); + +// Configuration file path +const CONFIG_FILE = path.join(__dirname, "build-config.json"); + +// Default configuration +const DEFAULT_CONFIG = { + scripts: [ + "setupBatteryOptimizations.js", + "notificationSounds.ts", + "setupExceptionHandler.js", + ], + // Add enabled: false to disable a script without removing it from the list + // Example: { path: "scriptName.js", enabled: false } +}; + +// Create default config if it doesn't exist +function ensureConfigExists() { + if (!fs.existsSync(CONFIG_FILE)) { + fs.writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2)); + console.log(`Created default build config at ${CONFIG_FILE}`); + } +} + +// Get the list of scripts to run +function getScriptsToRun() { + ensureConfigExists(); + const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8")); + return config.scripts + .filter((script) => { + if (typeof script === "string") return true; + return script.enabled !== false; + }) + .map((script) => { + if (typeof script === "string") return script; + return script.path; + }); +} + +// Run all enabled scripts +function runBuildScripts() { + console.log("πŸš€ Running build scripts..."); + + const scripts = getScriptsToRun(); + + scripts.forEach((scriptPath) => { + const fullPath = path.join(__dirname, scriptPath); + + if (!fs.existsSync(fullPath)) { + console.error(`❌ Script not found: ${fullPath}`); + return; + } + + console.log(`▢️ Running: ${scriptPath}`); + + try { + if (scriptPath.endsWith(".ts")) { + // For TypeScript files, use ts-node + execSync(`npx ts-node ${fullPath}`, { stdio: "inherit" }); + } else { + // For JavaScript files, use node + execSync(`node ${fullPath}`, { stdio: "inherit" }); + } + console.log(`βœ… Completed: ${scriptPath}`); + } catch (error) { + console.error(`❌ Error running ${scriptPath}:`, error.message); + } + }); + + console.log("✨ Build scripts completed"); +} + +// Run the function if this is the main module +if (require.main === module) { + runBuildScripts(); +} + +module.exports = runBuildScripts; diff --git a/scripts/setupExceptionHandler.js b/scripts/setupExceptionHandler.js new file mode 100644 index 0000000..51d743c --- /dev/null +++ b/scripts/setupExceptionHandler.js @@ -0,0 +1,211 @@ +const fs = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); + +// Get the app package name +function getPackageName() { + try { + const appJson = JSON.parse(fs.readFileSync("./app.json", "utf8")); + return ( + appJson.expo.android?.package || "com.codebuilderinc.codebuilderapps" + ); + } catch (error) { + console.error("Error getting package name:", error); + return "com.codebuilderinc.codebuilderapps"; // Fallback + } +} + +// Setup the TS global handler that works with your actual error service +function setupTSErrorHandler() { + console.log("πŸ“± Setting up TypeScript global error handler..."); + + const utilsDir = path.join("src", "utils"); + const handlerPath = path.join(utilsDir, "errorHandler.ts"); + + // Create utils directory if it doesn't exist + if (!fs.existsSync(utilsDir)) { + fs.mkdirSync(utilsDir, { recursive: true }); + } + + // Content for the error handler that uses your actual error reporting service + const handlerContent = ` +import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler'; +import { Alert } from 'react-native'; +import { errorReportingService } from '../services/errorReporting.service'; + +// JavaScript Error Handler +export const setupGlobalErrorHandlers = () => { + setJSExceptionHandler((error, isFatal) => { + // Log the error to your existing error reporting service + errorReportingService.submitError?.(error) || + errorReportingService.reportError?.(error) || + console.error('Caught JS Exception:', error); + + // Show a friendly message instead of crashing + Alert.alert( + 'Unexpected Error Occurred', + 'We encountered an issue. The app will continue running, and our team has been notified.', + [{ text: 'OK' }] + ); + }, true); + + // Native Exception Handler + setNativeExceptionHandler( + (exceptionString) => { + // Handle native exceptions + const error = new Error(\`Native Exception: \${exceptionString}\`); + errorReportingService.submitError?.(error) || + errorReportingService.reportError?.(error) || + console.error('Caught Native Exception:', exceptionString); + }, + false, // don't force app to quit + true // should catch all exceptions + ); +}; +`; + + // Write the file + fs.writeFileSync(handlerPath, handlerContent); + console.log(`βœ… Created error handler at ${handlerPath}`); +} + +// Setup the Android native exception handler +function setupNativeExceptionHandler() { + console.log("πŸ“± Setting up Android native exception handler..."); + + // Get package name and calculate path + const packageName = getPackageName(); + const packagePath = packageName.replace(/\./g, "/"); + + // Path to MainActivity.java + const mainActivityPath = path.join( + "android", + "app", + "src", + "main", + "java", + packagePath, + "MainActivity.java" + ); + + // Check if file exists + if (!fs.existsSync(mainActivityPath)) { + console.log( + `❌ MainActivity.java not found at ${mainActivityPath}. Run 'npx expo prebuild' first.` + ); + return; + } + + // Read the file + let mainActivityContent = fs.readFileSync(mainActivityPath, "utf8"); + + // Check if we've already modified this file + if (mainActivityContent.includes("// CUSTOM EXCEPTION HANDLER")) { + console.log("βœ… Exception handler already set up in MainActivity.java"); + return; + } + + // Find the onCreate method or prepare to add it + const onCreateRegex = + /protected void onCreate\s*\(\s*Bundle savedInstanceState\s*\)\s*\{/; + const hasOnCreate = onCreateRegex.test(mainActivityContent); + + let exceptionHandlerCode; + + if (hasOnCreate) { + // Append to existing onCreate method + exceptionHandlerCode = ` + // CUSTOM EXCEPTION HANDLER + final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + // Log the exception but don't crash the app + System.err.println("Caught unhandled exception: " + throwable.getMessage()); + throwable.printStackTrace(); + + // Don't call the default handler to prevent crash screen + // defaultHandler.uncaughtException(thread, throwable); + } + });`; + + // Insert the code after the opening brace of onCreate + mainActivityContent = mainActivityContent.replace( + onCreateRegex, + `protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState);${exceptionHandlerCode}` + ); + } else { + // Need to add the entire onCreate method + exceptionHandlerCode = ` + // CUSTOM EXCEPTION HANDLER + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + // Log the exception but don't crash the app + System.err.println("Caught unhandled exception: " + throwable.getMessage()); + throwable.printStackTrace(); + + // Don't call the default handler to prevent crash screen + // defaultHandler.uncaughtException(thread, throwable); + } + }); + } +`; + + // Add the import for Bundle if needed + if (!mainActivityContent.includes("import android.os.Bundle;")) { + mainActivityContent = mainActivityContent.replace( + "import com.facebook.react.ReactActivity;", + "import com.facebook.react.ReactActivity;\nimport android.os.Bundle;" + ); + } + + // Find the closing brace of the class and insert before it + const lastBraceIndex = mainActivityContent.lastIndexOf("}"); + mainActivityContent = + mainActivityContent.substring(0, lastBraceIndex) + + exceptionHandlerCode + + mainActivityContent.substring(lastBraceIndex); + } + + // Write the modified file + fs.writeFileSync(mainActivityPath, mainActivityContent); + console.log("βœ… Exception handler successfully added to MainActivity.java"); +} + +// Run both setup functions +function setupExceptionHandlers() { + // Setup the JS error handler utility + setupTSErrorHandler(); + + // Setup the native Android exception handler + setupNativeExceptionHandler(); + + // Reminder for necessary package + console.log(` +πŸ”” Next steps: +1. Add 'react-native-exception-handler' to your dependencies: + npm install react-native-exception-handler + +2. Import and use the handler in your _layout.tsx: + import { setupGlobalErrorHandlers } from '../src/utils/errorHandler'; + + // Add inside a useEffect: + useEffect(() => { + setupGlobalErrorHandlers(); + }, []); + `); +} + +// Run the function if this is the main module +if (require.main === module) { + setupExceptionHandlers(); +} + +module.exports = setupExceptionHandlers; From f2df8f7ecfea1f24a155987537b77f1368d55acd Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:14:43 -0500 Subject: [PATCH 06/26] global error handler file. --- utils/globalErrorhandler.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 utils/globalErrorhandler.ts diff --git a/utils/globalErrorhandler.ts b/utils/globalErrorhandler.ts new file mode 100644 index 0000000..8843a67 --- /dev/null +++ b/utils/globalErrorhandler.ts @@ -0,0 +1,32 @@ +import { + setJSExceptionHandler, + setNativeExceptionHandler, +} from "react-native-exception-handler"; +import { Alert } from "react-native"; +import { reportError } from "../services/errorReporting.service"; + +// JavaScript Error Handler +export const setupGlobalErrorHandlers = () => { + setJSExceptionHandler((error, isFatal) => { + // Report the error using your actual reportError function + reportError(error, { isFatal }); + + // Show a friendly message instead of crashing + Alert.alert( + "Unexpected Error Occurred", + "We encountered an issue. The app will continue running, and our team has been notified.", + [{ text: "OK" }] + ); + }, true); + + // Native Exception Handler + setNativeExceptionHandler( + (exceptionString) => { + // This is called when native code throws an exception + const error = new Error(`Native Exception: ${exceptionString}`); + reportError(error, { isFatal: true }); + }, + false, // don't force app to quit + true // should catch all exceptions + ); +}; From f9a86f2446fc8b991e5fcb4d32b11f0f57388ba0 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:15:05 -0500 Subject: [PATCH 07/26] Installed react-native-exception-handler & updated scripts to use global build script. --- package-lock.json | 655 +++++++++++++++++++++++----------------------- package.json | 5 +- 2 files changed, 325 insertions(+), 335 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e46666..b2b9c3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,30 +115,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.27.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -272,16 +272,16 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -539,12 +539,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -945,16 +945,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.7.tgz", + "integrity": "sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.27.7", "globals": "^11.1.0" }, "engines": { @@ -981,12 +981,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.7.tgz", + "integrity": "sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.7" }, "engines": { "node": ">=6.9.0" @@ -1152,15 +1153,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.7.tgz", + "integrity": "sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" + "@babel/plugin-transform-destructuring": "^7.27.7", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.27.7" }, "engines": { "node": ">=6.9.0" @@ -1201,9 +1203,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1546,16 +1548,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1565,16 +1567,16 @@ }, "node_modules/@babel/traverse--for-generate-function-map": { "name": "@babel/traverse", - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1583,9 +1585,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1977,9 +1979,9 @@ } }, "node_modules/@expo/cli/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -2543,9 +2545,9 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.7.tgz", - "integrity": "sha512-1w5MBp6NdF51gPGp0HsCZt0QC82hZWo37wI9HfxhdQF/sN/92Mh4t30vaY7gjHe71T5QNyab00oxZH/wP0MDgQ==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.8.tgz", + "integrity": "sha512-vzORt1zrgjIYOKAmHk0SUka0rlYo0AIZpOF6019ms2XaQnxyvcQACz1BxTk2++fTAzSAxerufNQOm2owPcAz+g==", "license": "MIT", "dependencies": { "@expo/config": "~11.0.10", @@ -2638,15 +2640,15 @@ } }, "node_modules/@firebase/ai": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.0.tgz", - "integrity": "sha512-wvF33gtU6TXb6Co8TEC1pcl4dnVstYmRE/vs9XjUGE7he7Sgf5TqSu+EoXk/fuzhw5tKr1LC5eG9KdYFM+eosw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.1.tgz", + "integrity": "sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2658,15 +2660,15 @@ } }, "node_modules/@firebase/analytics": { - "version": "0.10.16", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.16.tgz", - "integrity": "sha512-cMtp19He7Fd6uaj/nDEul+8JwvJsN8aRSJyuA1QN3QrKvfDDp+efjVurJO61sJpkVftw9O9nNMdhFbRcTmTfRQ==", + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz", + "integrity": "sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/installations": "0.6.17", + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2674,15 +2676,15 @@ } }, "node_modules/@firebase/analytics-compat": { - "version": "0.2.22", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.22.tgz", - "integrity": "sha512-VogWHgwkdYhjWKh8O1XU04uPrRaiDihkWvE/EMMmtWtaUtVALnpLnUurc3QtSKdPnvTz5uaIGKlW84DGtSPFbw==", + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.23.tgz", + "integrity": "sha512-3AdO10RN18G5AzREPoFgYhW6vWXr3u+OYQv6pl3CX6Fky8QRk0AHurZlY3Q1xkXO0TDxIsdhO3y65HF7PBOJDw==", "license": "Apache-2.0", "dependencies": { - "@firebase/analytics": "0.10.16", + "@firebase/analytics": "0.10.17", "@firebase/analytics-types": "0.8.3", - "@firebase/component": "0.6.17", - "@firebase/util": "1.12.0", + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2696,14 +2698,14 @@ "license": "Apache-2.0" }, "node_modules/@firebase/app": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.1.tgz", - "integrity": "sha512-0O33PKrXLoIWkoOO5ByFaLjZehBctSYWnb+xJkIdx2SKP/K9l1UPFXPwASyrOIqyY3ws+7orF/1j7wI5EKzPYQ==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz", + "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -2712,14 +2714,14 @@ } }, "node_modules/@firebase/app-check": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.0.tgz", - "integrity": "sha512-AZlRlVWKcu8BH4Yf8B5EI8sOi2UNGTS8oMuthV45tbt6OVUTSQwFPIEboZzhNJNKY+fPsg7hH8vixUWFZ3lrhw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.1.tgz", + "integrity": "sha512-MgNdlms9Qb0oSny87pwpjKush9qUwCJhfmTJHDfrcKo4neLGiSeVE4qJkzP7EQTIUFKp84pbTxobSAXkiuQVYQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2730,16 +2732,16 @@ } }, "node_modules/@firebase/app-check-compat": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.25.tgz", - "integrity": "sha512-3zrsPZWAKfV7DVC20T2dgfjzjtQnSJS65OfMOiddMUtJL1S5i0nAZKsdX0bOEvvrd0SBIL8jYnfpfDeQRnhV3w==", + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.26.tgz", + "integrity": "sha512-PkX+XJMLDea6nmnopzFKlr+s2LMQGqdyT2DHdbx1v1dPSqOol2YzgpgymmhC67vitXVpNvS3m/AiWQWWhhRRPQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-check": "0.10.0", + "@firebase/app-check": "0.10.1", "@firebase/app-check-types": "0.5.3", - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2762,15 +2764,15 @@ "license": "Apache-2.0" }, "node_modules/@firebase/app-compat": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.1.tgz", - "integrity": "sha512-9VGjnY23Gc1XryoF/ABWtZVJYnaPOnjHM7dsqq9YALgKRtxI1FryvELUVkDaEIUf4In2bfkb9ZENF1S9M273Dw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz", + "integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/app": "0.13.1", - "@firebase/component": "0.6.17", + "@firebase/app": "0.13.2", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2784,14 +2786,14 @@ "license": "Apache-2.0" }, "node_modules/@firebase/auth": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.7.tgz", - "integrity": "sha512-77o0aBKCfchdL1gkahARdawHyYefh+wRYn7o60tbwW6bfJNq2idbrRb3WSYCT4yBKWL0+9kKdwxBHPZ6DEiB+g==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz", + "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2808,15 +2810,15 @@ } }, "node_modules/@firebase/auth-compat": { - "version": "0.5.27", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.27.tgz", - "integrity": "sha512-axZx/MgjNO7uPA8/nMQiuVotGCngUFMppt5w0pxFIoIPD0kac0bsFdSEh5S2ttuEE0Aq1iUB6Flzwn+wvMgXnQ==", + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.28.tgz", + "integrity": "sha512-HpMSo/cc6Y8IX7bkRIaPPqT//Jt83iWy5rmDWeThXQCAImstkdNo3giFLORJwrZw2ptiGkOij64EH1ztNJzc7Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/auth": "1.10.7", + "@firebase/auth": "1.10.8", "@firebase/auth-types": "0.13.0", - "@firebase/component": "0.6.17", - "@firebase/util": "1.12.0", + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2843,12 +2845,12 @@ } }, "node_modules/@firebase/component": { - "version": "0.6.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.17.tgz", - "integrity": "sha512-M6DOg7OySrKEFS8kxA3MU5/xc37fiOpKPMz6cTsMUcsuKB6CiZxxNAvgFta8HGRgEpZbi8WjGIj6Uf+TpOhyzg==", + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz", + "integrity": "sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2856,15 +2858,15 @@ } }, "node_modules/@firebase/data-connect": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.9.tgz", - "integrity": "sha512-B5tGEh5uQrQeH0i7RvlU8kbZrKOJUmoyxVIX4zLA8qQJIN6A7D+kfBlGXtSwbPdrvyaejcRPcbOtqsDQ9HPJKw==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.10.tgz", + "integrity": "sha512-VMVk7zxIkgwlVQIWHOKFahmleIjiVFwFOjmakXPd/LDgaB/5vzwsB5DWIYo+3KhGxWpidQlR8geCIn39YflJIQ==", "license": "Apache-2.0", "dependencies": { "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2872,16 +2874,16 @@ } }, "node_modules/@firebase/database": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.19.tgz", - "integrity": "sha512-khE+MIYK+XlIndVn/7mAQ9F1fwG5JHrGKaG72hblCC6JAlUBDd3SirICH6SMCf2PQ0iYkruTECth+cRhauacyQ==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.20.tgz", + "integrity": "sha512-H9Rpj1pQ1yc9+4HQOotFGLxqAXwOzCHsRSRjcQFNOr8lhUt6LeYjf0NSRL04sc4X0dWe8DsCvYKxMYvFG/iOJw==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "faye-websocket": "0.11.4", "tslib": "^2.1.0" }, @@ -2890,16 +2892,16 @@ } }, "node_modules/@firebase/database-compat": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.10.tgz", - "integrity": "sha512-3sjl6oGaDDYJw/Ny0E5bO6v+KM3KoD4Qo/sAfHGdRFmcJ4QnfxOX9RbG9+ce/evI3m64mkPr24LlmTDduqMpog==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.11.tgz", + "integrity": "sha512-itEsHARSsYS95+udF/TtIzNeQ0Uhx4uIna0sk4E0wQJBUnLc/G1X6D7oRljoOuwwCezRLGvWBRyNrugv/esOEw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/database": "1.0.19", - "@firebase/database-types": "1.0.14", + "@firebase/component": "0.6.18", + "@firebase/database": "1.0.20", + "@firebase/database-types": "1.0.15", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2907,24 +2909,24 @@ } }, "node_modules/@firebase/database-types": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.14.tgz", - "integrity": "sha512-8a0Q1GrxM0akgF0RiQHliinhmZd+UQPrxEmUv7MnQBYfVFiLtKOgs3g6ghRt/WEGJHyQNslZ+0PocIwNfoDwKw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.15.tgz", + "integrity": "sha512-XWHJ0VUJ0k2E9HDMlKxlgy/ZuTa9EvHCGLjaKSUvrQnwhgZuRU5N3yX6SZ+ftf2hTzZmfRkv+b3QRvGg40bKNw==", "license": "Apache-2.0", "dependencies": { "@firebase/app-types": "0.9.3", - "@firebase/util": "1.12.0" + "@firebase/util": "1.12.1" } }, "node_modules/@firebase/firestore": { - "version": "4.7.17", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.17.tgz", - "integrity": "sha512-YhXWA7HlSnekExhZ5u4i0e+kpPxsh/qMrzeNDgsAva71JXK8OOuOx+yLyYBFhmu3Hr5JJDO2fsZA/wrWoQYHDg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz", + "integrity": "sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "@firebase/webchannel-wrapper": "1.0.3", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", @@ -2938,15 +2940,15 @@ } }, "node_modules/@firebase/firestore-compat": { - "version": "0.3.52", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.52.tgz", - "integrity": "sha512-nzt3Sag+EBdm1Jkw/FnnKBPk0LpUUxOlMHMADPBXYhhXrLszxn1+vb64nJsbgRIHfsCn+rg8gyGrb+8frzXrjg==", + "version": "0.3.53", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.53.tgz", + "integrity": "sha512-qI3yZL8ljwAYWrTousWYbemay2YZa+udLWugjdjju2KODWtLG94DfO4NALJgPLv8CVGcDHNFXoyQexdRA0Cz8Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/firestore": "4.7.17", + "@firebase/component": "0.6.18", + "@firebase/firestore": "4.8.0", "@firebase/firestore-types": "3.0.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2967,16 +2969,16 @@ } }, "node_modules/@firebase/functions": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.8.tgz", - "integrity": "sha512-p+ft6dQW0CJ3BLLxeDb5Hwk9ARw01kHTZjLqiUdPRzycR6w7Z75ThkegNmL6gCss3S0JEpldgvehgZ3kHybVhA==", + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.9.tgz", + "integrity": "sha512-FG95w6vjbUXN84Ehezc2SDjGmGq225UYbHrb/ptkRT7OTuCiQRErOQuyt1jI1tvcDekdNog+anIObihNFz79Lg==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -2987,15 +2989,15 @@ } }, "node_modules/@firebase/functions-compat": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.25.tgz", - "integrity": "sha512-V0JKUw5W/7aznXf9BQ8LIYHCX6zVCM8Hdw7XUQ/LU1Y9TVP8WKRCnPB/qdPJ0xGjWWn7fhtwIYbgEw/syH4yTQ==", + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.26.tgz", + "integrity": "sha512-A798/6ff5LcG2LTWqaGazbFYnjBW8zc65YfID/en83ALmkhu2b0G8ykvQnLtakbV9ajrMYPn7Yc/XcYsZIUsjA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/functions": "0.12.8", + "@firebase/component": "0.6.18", + "@firebase/functions": "0.12.9", "@firebase/functions-types": "0.6.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -3012,13 +3014,13 @@ "license": "Apache-2.0" }, "node_modules/@firebase/installations": { - "version": "0.6.17", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.17.tgz", - "integrity": "sha512-zfhqCNJZRe12KyADtRrtOj+SeSbD1H/K8J24oQAJVv/u02eQajEGlhZtcx9Qk7vhGWF5z9dvIygVDYqLL4o1XQ==", + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz", + "integrity": "sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/util": "1.12.0", + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -3027,15 +3029,15 @@ } }, "node_modules/@firebase/installations-compat": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.17.tgz", - "integrity": "sha512-J7afeCXB7yq25FrrJAgbx8mn1nG1lZEubOLvYgG7ZHvyoOCK00sis5rj7TgDrLYJgdj/SJiGaO1BD3BAp55TeA==", + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.18.tgz", + "integrity": "sha512-aLFohRpJO5kKBL/XYL4tN+GdwEB/Q6Vo9eZOM/6Kic7asSUgmSfGPpGUZO1OAaSRGwF4Lqnvi1f/f9VZnKzChw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/installations": "0.6.17", + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", "@firebase/installations-types": "0.5.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3064,15 +3066,15 @@ } }, "node_modules/@firebase/messaging": { - "version": "0.12.21", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.21.tgz", - "integrity": "sha512-bYJ2Evj167Z+lJ1ach6UglXz5dUKY1zrJZd15GagBUJSR7d9KfiM1W8dsyL0lDxcmhmA/sLaBYAAhF1uilwN0g==", + "version": "0.12.22", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz", + "integrity": "sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/installations": "0.6.17", + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -3081,14 +3083,14 @@ } }, "node_modules/@firebase/messaging-compat": { - "version": "0.2.21", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.21.tgz", - "integrity": "sha512-1yMne+4BGLbHbtyu/VyXWcLiefUE1+K3ZGfVTyKM4BH4ZwDFRGoWUGhhx+tKRX4Tu9z7+8JN67SjnwacyNWK5g==", + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.22.tgz", + "integrity": "sha512-5ZHtRnj6YO6f/QPa/KU6gryjmX4Kg33Kn4gRpNU6M1K47Gm8kcQwPkX7erRUYEH1mIWptfvjvXMHWoZaWjkU7A==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/messaging": "0.12.21", - "@firebase/util": "1.12.0", + "@firebase/component": "0.6.18", + "@firebase/messaging": "0.12.22", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3102,15 +3104,15 @@ "license": "Apache-2.0" }, "node_modules/@firebase/performance": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.6.tgz", - "integrity": "sha512-AsOz74dSTlyQGlnnbLWXiHFAsrxhpssPOsFFi4HgOJ5DjzkK7ZdZ/E9uMPrwFoXJyMVoybGRuqsL/wkIbFITsA==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.7.tgz", + "integrity": "sha512-JTlTQNZKAd4+Q5sodpw6CN+6NmwbY72av3Lb6wUKTsL7rb3cuBIhQSrslWbVz0SwK3x0ZNcqX24qtRbwKiv+6w==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/installations": "0.6.17", + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0", "web-vitals": "^4.2.4" }, @@ -3119,16 +3121,16 @@ } }, "node_modules/@firebase/performance-compat": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.19.tgz", - "integrity": "sha512-4cU0T0BJ+LZK/E/UwFcvpBCVdkStgBMQwBztM9fJPT6udrEUk3ugF5/HT+E2Z22FCXtIaXDukJbYkE/c3c6IHw==", + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.20.tgz", + "integrity": "sha512-XkFK5NmOKCBuqOKWeRgBUFZZGz9SzdTZp4OqeUg+5nyjapTiZ4XoiiUL8z7mB2q+63rPmBl7msv682J3rcDXIQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/performance": "0.7.6", + "@firebase/performance": "0.7.7", "@firebase/performance-types": "0.2.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3142,15 +3144,15 @@ "license": "Apache-2.0" }, "node_modules/@firebase/remote-config": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.4.tgz", - "integrity": "sha512-ZyLJRT46wtycyz2+opEkGaoFUOqRQjt/0NX1WfUISOMCI/PuVoyDjqGpq24uK+e8D5NknyTpiXCVq5dowhScmg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.5.tgz", + "integrity": "sha512-fU0c8HY0vrVHwC+zQ/fpXSqHyDMuuuglV94VF6Yonhz8Fg2J+KOowPGANM0SZkLvVOYpTeWp3ZmM+F6NjwWLnw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/installations": "0.6.17", + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3158,16 +3160,16 @@ } }, "node_modules/@firebase/remote-config-compat": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.17.tgz", - "integrity": "sha512-KelsBD0sXSC0u3esr/r6sJYGRN6pzn3bYuI/6pTvvmZbjBlxQkRabHAVH6d+YhLcjUXKIAYIjZszczd1QJtOyA==", + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.18.tgz", + "integrity": "sha512-YiETpldhDy7zUrnS8e+3l7cNs0sL7+tVAxvVYU0lu7O+qLHbmdtAxmgY+wJqWdW2c9nDvBFec7QiF58pEUu0qQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", + "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", - "@firebase/remote-config": "0.6.4", + "@firebase/remote-config": "0.6.5", "@firebase/remote-config-types": "0.4.0", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3181,13 +3183,13 @@ "license": "Apache-2.0" }, "node_modules/@firebase/storage": { - "version": "0.13.13", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.13.tgz", - "integrity": "sha512-E+MTNcBgpoAynicgVb2ZsHCuEOO4aAiUX5ahNwe/1dEyZpo2H4DwFqKQRNK/sdAIgBbjBwcfV2p0MdPFGIR0Ew==", + "version": "0.13.14", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.14.tgz", + "integrity": "sha512-xTq5ixxORzx+bfqCpsh+o3fxOsGoDjC1nO0Mq2+KsOcny3l7beyBhP/y1u5T6mgsFQwI1j6oAkbT5cWdDBx87g==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/util": "1.12.0", + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -3198,15 +3200,15 @@ } }, "node_modules/@firebase/storage-compat": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.23.tgz", - "integrity": "sha512-B/ufkT/R/tSvc2av+vP6ZYybGn26FwB9YVDYg/6Bro+5TN3VEkCeNmfnX3XLa2DSdXUTZAdWCbMxW0povGa4MA==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.24.tgz", + "integrity": "sha512-XHn2tLniiP7BFKJaPZ0P8YQXKiVJX+bMyE2j2YWjYfaddqiJnROJYqSomwW6L3Y+gZAga35ONXUJQju6MB6SOQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/storage": "0.13.13", + "@firebase/component": "0.6.18", + "@firebase/storage": "0.13.14", "@firebase/storage-types": "0.8.3", - "@firebase/util": "1.12.0", + "@firebase/util": "1.12.1", "tslib": "^2.1.0" }, "engines": { @@ -3227,9 +3229,9 @@ } }, "node_modules/@firebase/util": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz", - "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz", + "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4467,17 +4469,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz", + "integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -4489,19 +4487,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.9.tgz", + "integrity": "sha512-amBU75CKOOkcQLfyM6J+DnWwz41yTsWI7o8MQ003LwUIWb4NYX/evAblTx1oBBYJySqL/zHPxHXDw5ewpQaUFw==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4509,15 +4498,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz", + "integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz", + "integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -6140,9 +6129,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", - "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "version": "24.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", + "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -6463,22 +6452,22 @@ ] }, "node_modules/@urql/core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.1.1.tgz", - "integrity": "sha512-aGh024z5v2oINGD/In6rAtVKTm4VmQ2TxKQBAtk2ZSME5dunZFcjltw4p5ENQg+5CBhZ3FHMzl0Oa+rwqiWqlg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz", + "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==", "license": "MIT", "dependencies": { - "@0no-co/graphql.web": "^1.0.5", + "@0no-co/graphql.web": "^1.0.13", "wonka": "^6.3.2" } }, "node_modules/@urql/exchange-retry": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.1.tgz", - "integrity": "sha512-EEmtFu8JTuwsInqMakhLq+U3qN8ZMd5V3pX44q0EqD2imqTDsa8ikZqJ1schVrN8HljOdN+C08cwZ1/r5uIgLw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz", + "integrity": "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==", "license": "MIT", "dependencies": { - "@urql/core": "^5.1.1", + "@urql/core": "^5.1.2", "wonka": "^6.3.2" }, "peerDependencies": { @@ -7067,13 +7056,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -7103,12 +7092,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -9022,9 +9011,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -9051,9 +9040,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -9392,9 +9381,9 @@ } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -9445,9 +9434,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.175", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.175.tgz", - "integrity": "sha512-Nqpef9mOVo7pZfl9NIUhj7tgtRTsMzCzRTJDP1ccim4Wb4YHOz3Le87uxeZq68OCNwau2iQ/X7UwdAZ3ReOkmg==", + "version": "1.5.178", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz", + "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==", "license": "ISC" }, "node_modules/emittery": { @@ -9951,9 +9940,9 @@ } }, "node_modules/expo-camera": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-16.1.8.tgz", - "integrity": "sha512-NpBbkUhHG6cs2TNUQBFSEtXb5j1/kTPIhiuqBcHosZG2yb/8MuM/ii4McJaqfe/6pn0YPqkH4k0Uod11DOSLmw==", + "version": "16.1.9", + "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-16.1.9.tgz", + "integrity": "sha512-Aao0rNhhJxrWLx1moWZYBsRnEoZ04Vnm8uPO24V1161m5PLGptI7m5NS8NKoHx9McfrnDkvyJSWxz/7AhfEzTw==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -10005,12 +9994,12 @@ } }, "node_modules/expo-dev-client": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.2.1.tgz", - "integrity": "sha512-SzrHvXeyTGawzc/7ZIHFmaUYiCeRJagL9bJo/yTPmxdycFFOOdLs1FNMFXyYhB6YY4u5EKTCO6g1fug+0GV9sQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.2.2.tgz", + "integrity": "sha512-shOMVwnfpq8ptpix+KveFz9UWv4SNxqxFYkEvRfsgyLiUG8O1vXE0FaZUNZgF+idOPGuO6C34YZM6pLzE0Dd6A==", "license": "MIT", "dependencies": { - "expo-dev-launcher": "5.1.13", + "expo-dev-launcher": "5.1.14", "expo-dev-menu": "6.1.12", "expo-dev-menu-interface": "1.10.0", "expo-manifests": "~0.16.5", @@ -10021,9 +10010,9 @@ } }, "node_modules/expo-dev-launcher": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.13.tgz", - "integrity": "sha512-EAxkI0MOZk1E9tkk+QpyDhqlCjUqAr8q+mobcC3ZJIIi7KejhaQlGVlF1kUUITsYLvFvbT8egRgrsMO57T6uDA==", + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.14.tgz", + "integrity": "sha512-pxXqn8xCgOuc+W32mRJ9DiVK7O0zmGTG/ePd3kw53Xg1PKB9iOeYNty53qZEkIWjt8xdqS0JdECQT4qjgA2d1g==", "license": "MIT", "dependencies": { "ajv": "8.11.0", @@ -10246,13 +10235,13 @@ } }, "node_modules/expo-router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.0.tgz", - "integrity": "sha512-mnKpw35W6kKPpZm+ZxQei6HGUx2JO3znzqJZInzqrTZMgfAcHGgvP9AQFjg/Qi/Qy1CxunB9aQnqE9JPbSwbpw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.1.tgz", + "integrity": "sha512-KYAp/SwkPVgY+8OI+UPGENZG4j+breoOMXmZ01s99U7X0dpSihKGSNpK6LkEoU31MXMLuUHGYYwD00zm9aqcSg==", "license": "MIT", "dependencies": { "@expo/metro-runtime": "5.0.4", - "@expo/server": "^0.6.2", + "@expo/server": "^0.6.3", "@radix-ui/react-slot": "1.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", @@ -10751,39 +10740,39 @@ } }, "node_modules/firebase": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.9.1.tgz", - "integrity": "sha512-nbQbQxNlkHHRDn4cYwHdAKHwJPeZ0jRXxlNp6PCOb9CQx8Dc6Vjve97R34r1EZJnzOsPYZ3+ssJH7fkovDjvCw==", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz", + "integrity": "sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w==", "license": "Apache-2.0", "dependencies": { - "@firebase/ai": "1.4.0", - "@firebase/analytics": "0.10.16", - "@firebase/analytics-compat": "0.2.22", - "@firebase/app": "0.13.1", - "@firebase/app-check": "0.10.0", - "@firebase/app-check-compat": "0.3.25", - "@firebase/app-compat": "0.4.1", + "@firebase/ai": "1.4.1", + "@firebase/analytics": "0.10.17", + "@firebase/analytics-compat": "0.2.23", + "@firebase/app": "0.13.2", + "@firebase/app-check": "0.10.1", + "@firebase/app-check-compat": "0.3.26", + "@firebase/app-compat": "0.4.2", "@firebase/app-types": "0.9.3", - "@firebase/auth": "1.10.7", - "@firebase/auth-compat": "0.5.27", - "@firebase/data-connect": "0.3.9", - "@firebase/database": "1.0.19", - "@firebase/database-compat": "2.0.10", - "@firebase/firestore": "4.7.17", - "@firebase/firestore-compat": "0.3.52", - "@firebase/functions": "0.12.8", - "@firebase/functions-compat": "0.3.25", - "@firebase/installations": "0.6.17", - "@firebase/installations-compat": "0.2.17", - "@firebase/messaging": "0.12.21", - "@firebase/messaging-compat": "0.2.21", - "@firebase/performance": "0.7.6", - "@firebase/performance-compat": "0.2.19", - "@firebase/remote-config": "0.6.4", - "@firebase/remote-config-compat": "0.2.17", - "@firebase/storage": "0.13.13", - "@firebase/storage-compat": "0.3.23", - "@firebase/util": "1.12.0" + "@firebase/auth": "1.10.8", + "@firebase/auth-compat": "0.5.28", + "@firebase/data-connect": "0.3.10", + "@firebase/database": "1.0.20", + "@firebase/database-compat": "2.0.11", + "@firebase/firestore": "4.8.0", + "@firebase/firestore-compat": "0.3.53", + "@firebase/functions": "0.12.9", + "@firebase/functions-compat": "0.3.26", + "@firebase/installations": "0.6.18", + "@firebase/installations-compat": "0.2.18", + "@firebase/messaging": "0.12.22", + "@firebase/messaging-compat": "0.2.22", + "@firebase/performance": "0.7.7", + "@firebase/performance-compat": "0.2.20", + "@firebase/remote-config": "0.6.5", + "@firebase/remote-config-compat": "0.2.18", + "@firebase/storage": "0.13.14", + "@firebase/storage-compat": "0.3.24", + "@firebase/util": "1.12.1" } }, "node_modules/flow-enums-runtime": { @@ -14457,9 +14446,9 @@ } }, "node_modules/jsdom/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { @@ -16074,9 +16063,9 @@ } }, "node_modules/napi-postinstall": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", - "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", + "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==", "dev": true, "license": "MIT", "bin": { @@ -17194,9 +17183,9 @@ } }, "node_modules/react-devtools-core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.2.tgz", - "integrity": "sha512-ldFwzufLletzCikNJVYaxlxMLu7swJ3T2VrGfzXlMsVhZhPDKXA38DEROidaYZVgMAmQnIjymrmqto5pyfrwPA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.3.tgz", + "integrity": "sha512-4be9IVco12d/4D7NpZgNjffbYIo/MAk4f5eBJR8PpKyiR7tgwe29liQbxyqDov5Ybc2crGABZyYAmdeU6NowKg==", "license": "MIT", "dependencies": { "shell-quote": "^1.6.1", @@ -19276,9 +19265,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", - "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "license": "MIT" }, "node_modules/tapable": { diff --git a/package.json b/package.json index 526a237..d7b8b11 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "private": true, "scripts": { "start": "expo start", - "prebuild": "node scripts/setupBatteryOptimization.js && node scripts/notificationSounds.js", + "prebuild": "node scripts/setupExceptionHandler.js", + "eas-build-pre-install": "node scripts/setupExceptionHandler.js", "android": "npm run prebuild && expo run:android", "bump-version": "node scripts/bumpVersion.js", "eas-build-preinstall": "node scripts/setupBatteryOptimization.js && node scripts/notificationSounds.js", @@ -94,4 +95,4 @@ } } } -} +} \ No newline at end of file From 29972432d7391d97d605d0a29e640b98a240d7e5 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 16:16:06 -0500 Subject: [PATCH 08/26] Fixed error on line 185. --- .github/workflows/eas-android-build.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/eas-android-build.yml b/.github/workflows/eas-android-build.yml index 3dad552..cb031e7 100644 --- a/.github/workflows/eas-android-build.yml +++ b/.github/workflows/eas-android-build.yml @@ -4,7 +4,7 @@ permissions: on: push: - branches: ['**'] # run on any branch + branches: ["**"] # run on any branch workflow_dispatch: env: @@ -21,8 +21,8 @@ jobs: build_number: ${{ steps.version-control.outputs.build_number }} build_date: ${{ steps.version-control.outputs.build_date }} is_production: ${{ steps.version-control.outputs.is_production }} - eas_profile: ${{ steps.version-control.outputs.eas_profile }} # added dynamic profile output - eas_channel: ${{ steps.version-control.outputs.eas_channel }} # added dynamic channel output + eas_profile: ${{ steps.version-control.outputs.eas_profile }} # added dynamic profile output + eas_channel: ${{ steps.version-control.outputs.eas_channel }} # added dynamic channel output steps: # ======================== # πŸ› οΈ Repository Setup @@ -181,8 +181,7 @@ jobs: SLEEP_TIME=30 while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do - echo -e " -=== Attempt $((RETRY_COUNT+1))/$MAX_RETRIES ===" + echo -e "=== Attempt $((RETRY_COUNT+1))/$MAX_RETRIES ===" # Fetch build status in JSON format BUILD_STATUS_JSON=$(npx eas build:view --json $BUILD_ID) @@ -341,4 +340,4 @@ jobs: name: ${{ steps.release-type.outputs.RELEASE_TITLE }} body_path: changelog.txt files: app-release.apk - prerelease: ${{ needs.build-android.outputs.is_production != 'true' }} \ No newline at end of file + prerelease: ${{ needs.build-android.outputs.is_production != 'true' }} From 00d75662148c1c5e92de693f7fbf78b7a981e894 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Mon, 30 Jun 2025 18:38:50 -0500 Subject: [PATCH 09/26] Prerelease / nightly /staging build workflow updates. --- .github/workflows/eas-android-build.yml | 72 +++++++++++++++---------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/.github/workflows/eas-android-build.yml b/.github/workflows/eas-android-build.yml index cb031e7..84c851b 100644 --- a/.github/workflows/eas-android-build.yml +++ b/.github/workflows/eas-android-build.yml @@ -4,7 +4,7 @@ permissions: on: push: - branches: ["**"] # run on any branch + branches: ["**"] # Run on all branches workflow_dispatch: env: @@ -21,8 +21,7 @@ jobs: build_number: ${{ steps.version-control.outputs.build_number }} build_date: ${{ steps.version-control.outputs.build_date }} is_production: ${{ steps.version-control.outputs.is_production }} - eas_profile: ${{ steps.version-control.outputs.eas_profile }} # added dynamic profile output - eas_channel: ${{ steps.version-control.outputs.eas_channel }} # added dynamic channel output + branch_name: ${{ steps.extract-branch.outputs.branch_name }} steps: # ======================== # πŸ› οΈ Repository Setup @@ -32,6 +31,14 @@ jobs: with: fetch-depth: 0 + - name: "πŸ” Extract branch name" + id: extract-branch + shell: bash + run: | + BRANCH_NAME=${GITHUB_REF#refs/heads/} + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + echo "Branch: $BRANCH_NAME" + # ======================== # βš™οΈ Environment Configuration # ======================== @@ -70,18 +77,18 @@ jobs: - name: "🏷️ Set CI/CD Versions" id: version-control run: | - # Determine branch and environment - BRANCH_NAME=${GITHUB_REF#refs/heads/} - if [ "${BRANCH_NAME}" == "main" ]; then + # Use version from version.json (requires jq) + if [ "${{ github.ref }}" == "refs/heads/main" ]; then APP_VERSION=$(jq -r '.version' version.json) IS_PRODUCTION="true" - EAS_PROFILE=production - EAS_CHANNEL=production else - APP_VERSION="1.0.0-prerelease.${GITHUB_RUN_NUMBER}" + # For non-main branches, create a prerelease version with branch name + BRANCH_NAME=${{ steps.extract-branch.outputs.branch_name }} + SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9]/-/g') + # Get base version from version.json + BASE_VERSION=$(jq -r '.version' version.json) + APP_VERSION="${BASE_VERSION}-pre.${SANITIZED_BRANCH}.${{ github.run_number }}" IS_PRODUCTION="false" - EAS_PROFILE=development # dev build profile - EAS_CHANNEL="dev-${BRANCH_NAME}" # dev release channel fi # Generate build identifiers @@ -89,19 +96,15 @@ jobs: BUILD_DATE=$(date +'%Y%m%d-%H%M%S') # Set outputs for downstream jobs - echo "app_version=${APP_VERSION}" >> $GITHUB_OUTPUT - echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT - echo "build_date=${BUILD_DATE}" >> $GITHUB_OUTPUT - echo "is_production=${IS_PRODUCTION}" >> $GITHUB_OUTPUT - echo "eas_profile=${EAS_PROFILE}" >> $GITHUB_OUTPUT - echo "eas_channel=${EAS_CHANNEL}" >> $GITHUB_OUTPUT + echo "app_version=$APP_VERSION" >> $GITHUB_OUTPUT + echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT + echo "build_date=$BUILD_DATE" >> $GITHUB_OUTPUT + echo "is_production=$IS_PRODUCTION" >> $GITHUB_OUTPUT # Export environment variables - echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_ENV - echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV - echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV - echo "EAS_PROFILE=${EAS_PROFILE}" >> $GITHUB_ENV - echo "EAS_CHANNEL=${EAS_CHANNEL}" >> $GITHUB_ENV + echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV + echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV + echo "BUILD_DATE=$BUILD_DATE" >> $GITHUB_ENV # ======================== # πŸ” EAS Setup & Auth @@ -118,9 +121,19 @@ jobs: - name: "πŸš€ Trigger EAS Build" id: build run: | - echo "πŸ”„ Initializing build process with profile=${EAS_PROFILE} on channel=${EAS_CHANNEL}..." + echo "πŸ”„ Initializing build process..." sudo apt-get install -y jq - BUILD_JSON=$(npx eas build -p android --profile $EAS_PROFILE --channel $EAS_CHANNEL --non-interactive --json) + + # Choose profile based on branch + if [ "${{ github.ref }}" == "refs/heads/main" ]; then + BUILD_PROFILE="production" + else + BUILD_PROFILE="preview" # Use a different profile for pre-releases if needed + fi + + echo "Using build profile: $BUILD_PROFILE" + + BUILD_JSON=$(npx eas build -p android --profile $BUILD_PROFILE --non-interactive --json) echo "Raw build output: $BUILD_JSON" BUILD_ID=$(echo "$BUILD_JSON" | jq -r '.[0].id') if [[ -z "$BUILD_ID" || "$BUILD_ID" == "null" ]]; then @@ -181,7 +194,7 @@ jobs: SLEEP_TIME=30 while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do - echo -e "=== Attempt $((RETRY_COUNT+1))/$MAX_RETRIES ===" + echo -e "\n=== Attempt $((RETRY_COUNT+1))/$MAX_RETRIES ===" # Fetch build status in JSON format BUILD_STATUS_JSON=$(npx eas build:view --json $BUILD_ID) @@ -244,6 +257,7 @@ jobs: id: download run: | echo "πŸ”½ Retrieving APK URL..." + # Use the build:view command to get a clean JSON response APK_URL=$(npx eas build:view --json ${{ needs.build-android.outputs.build_id }} | jq -r '.artifacts.buildUrl') if [[ -z "$APK_URL" || "$APK_URL" == "null" ]]; then echo "❌ Error: No APK URL found!" @@ -284,7 +298,6 @@ jobs: CHANGELOG=$(git log --pretty=format:"- %s (%h) by %an" -n 15) echo "$CHANGELOG" > changelog.txt - # FIXED OUTPUT HANDLING (ONLY CHANGE) delimiter=$(openssl rand -hex 6) echo "CHANGELOG<<${delimiter}" >> $GITHUB_OUTPUT cat changelog.txt >> $GITHUB_OUTPUT @@ -326,9 +339,10 @@ jobs: RELEASE_TAG="v${{ needs.build-android.outputs.app_version }}" RELEASE_TITLE="Production Release v${{ needs.build-android.outputs.app_version }}" else - echo "🟑 Nightly build detected" - RELEASE_TAG="nightly-${{ needs.build-android.outputs.build_date }}" - RELEASE_TITLE="Nightly Build (${{ needs.build-android.outputs.build_date }})" + echo "🟑 Pre-release build detected" + BRANCH_NAME="${{ needs.build-android.outputs.branch_name }}" + RELEASE_TAG="prerelease-${BRANCH_NAME}-${{ needs.build-android.outputs.build_date }}" + RELEASE_TITLE="Pre-release (${BRANCH_NAME}) v${{ needs.build-android.outputs.app_version }}" fi echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_OUTPUT echo "RELEASE_TITLE=${RELEASE_TITLE}" >> $GITHUB_OUTPUT From eacbb14d7c30e011689c3062c47a54942bccb3c5 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:31:41 -0500 Subject: [PATCH 10/26] xcode / ios additions + other cleanup & optimizations. --- app.config.js | 273 +++++++++++++++++++++++++------------------------- 1 file changed, 136 insertions(+), 137 deletions(-) diff --git a/app.config.js b/app.config.js index eb3d6b2..736affe 100644 --- a/app.config.js +++ b/app.config.js @@ -1,154 +1,153 @@ -import dotenv from "dotenv"; -import withNotificationToolsReplace from "./plugins/test.cjs"; -import versionData from "./version.json"; -import { withPlugins } from "@expo/config-plugins"; -import { withXcodeProject } from "expo/config-plugins"; -import fs from "fs"; +import dotenv from 'dotenv'; +import withNotificationToolsReplace from './plugins/test.cjs'; +import versionData from './version.json'; +import { withPlugins } from '@expo/config-plugins'; +import { withXcodeProject } from 'expo/config-plugins'; +import fs from 'fs'; // Explicitly load the .env file dotenv.config(); // Add this new plugin const withIOSSounds = (config) => { - return withXcodeProject(config, async (cfg) => { - const xcodeProject = cfg.modResults; - const appName = "CodeBuilder Admin"; // Match your iOS project name - const soundFiles = fs.readdirSync(`./ios/${appName}/Sounds`); + return withXcodeProject(config, async (cfg) => { + const xcodeProject = cfg.modResults; + const appName = 'CodeBuilder Admin'; // Match your iOS project name + const soundFiles = fs.readdirSync(`./ios/${appName}/Sounds`); - soundFiles.forEach((file) => { - xcodeProject.addResourceFile({ - path: `${appName}/Sounds/${file}`, - group: "Resources", - }); - }); + soundFiles.forEach((file) => { + xcodeProject.addResourceFile({ + path: `${appName}/Sounds/${file}`, + group: 'Resources', + }); + }); - return cfg; - }); + return cfg; + }); }; module.exports = { - expo: { - name: "CodeBuilder Admin", - slug: "codebuilder", - version: versionData.version, // Using version from version.json - extra: { - buildDate: process.env.BUILD_DATE || new Date().toISOString(), - eas: { - projectId: "c382aeb5-b138-47fb-83b4-dc45ab02ce76", - }, - firebaseApiKey: process.env.FIREBASE_API_KEY, - firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN, - firebaseProjectId: process.env.FIREBASE_PROJECT_ID, - firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET, - firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - firebaseAppId: process.env.FIREBASE_APP_ID, - googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID, - }, - orientation: "portrait", - icon: "./assets/images/icon.png", - scheme: "codebuilder-admin", - userInterfaceStyle: "automatic", - newArchEnabled: true, - notification: { - icon: "./assets/images/icon.png", - color: "#ffffff", // Optional: Tint color for the icon - }, - splash: { - image: "./assets/images/splash-icon.png", - resizeMode: "contain", - backgroundColor: "#ffffff", - }, - ios: { - buildNumber: versionData.iosBuildNumber, // Using iOS build number from version.json - supportsTablet: true, - bundleIdentifier: "com.digitalnomad91.codebuilderadmin", - googleServicesFile: "./GoogleService-Info.plist", - entitlements: { - "aps-environment": "production", - }, - config: { - // Provide your Google Maps API key - googleMapsApiKey: "YOUR_IOS_GOOGLE_MAPS_API_KEY", - }, - }, - android: { - versionCode: versionData.androidVersionCode, // Using Android versionCode from version.json - adaptiveIcon: { - foregroundImage: "./assets/images/adaptive-icon.png", - backgroundColor: "#ffffff", - }, - package: "com.digitalnomad91.codebuilderadmin", - permissions: ["NOTIFICATIONS", "POST_NOTIFICATIONS", "READ_PHONE_STATE"], - googleServicesFile: "./google-services.json", - useNextNotificationsApi: true, - notification: { - icon: "./assets/images/icon.png", - color: "#ffffff", // Optional: Tint color for the icon - }, - config: { - googleMaps: { - apiKey: process.env.GOOGLE_MAPS_API_KEY, + expo: { + name: 'CodeBuilder Admin', + slug: 'codebuilder', + version: versionData.version, // Using version from version.json + extra: { + buildDate: process.env.BUILD_DATE || new Date().toISOString(), + eas: { + projectId: 'c382aeb5-b138-47fb-83b4-dc45ab02ce76', + }, + firebaseApiKey: process.env.FIREBASE_API_KEY, + firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN, + firebaseProjectId: process.env.FIREBASE_PROJECT_ID, + firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET, + firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, + firebaseAppId: process.env.FIREBASE_APP_ID, + googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID || process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID, }, - }, - // manifest: { - // application: { - // metaData: [ - // { - // "android:name": "com.google.firebase.messaging.default_notification_color", - // "android:resource": "@color/notification_icon_color", - // "tools:replace": "android:resource", - // }, - // ], - // }, - // }, - }, - web: { - bundler: "metro", - output: "web-build", - favicon: "./assets/images/favicon.png", - }, - plugins: [ - withNotificationToolsReplace, - [ - "expo-camera", - { - cameraPermission: "Allow $(PRODUCT_NAME) to access your camera", - microphonePermission: - "Allow $(PRODUCT_NAME) to access your microphone", - recordAudioAndroid: true, + orientation: 'portrait', + icon: './assets/images/icon.png', + scheme: 'codebuilder-admin', + userInterfaceStyle: 'automatic', + newArchEnabled: true, + notification: { + icon: './assets/images/icon.png', + color: '#ffffff', // Optional: Tint color for the icon + }, + splash: { + image: './assets/images/splash-icon.png', + resizeMode: 'contain', + backgroundColor: '#ffffff', + }, + ios: { + buildNumber: versionData.iosBuildNumber, // Using iOS build number from version.json + supportsTablet: true, + bundleIdentifier: 'com.digitalnomad91.codebuilderadmin', + googleServicesFile: './GoogleService-Info.plist', + entitlements: { + 'aps-environment': 'production', + }, + config: { + // Provide your Google Maps API key + googleMapsApiKey: 'YOUR_IOS_GOOGLE_MAPS_API_KEY', + }, + }, + android: { + versionCode: versionData.androidVersionCode, // Using Android versionCode from version.json + adaptiveIcon: { + foregroundImage: './assets/images/adaptive-icon.png', + backgroundColor: '#ffffff', + }, + package: 'com.digitalnomad91.codebuilderadmin', + permissions: ['NOTIFICATIONS', 'POST_NOTIFICATIONS', 'READ_PHONE_STATE'], + googleServicesFile: './google-services.json', + useNextNotificationsApi: true, + notification: { + icon: './assets/images/icon.png', + color: '#ffffff', // Optional: Tint color for the icon + }, + config: { + googleMaps: { + apiKey: process.env.GOOGLE_MAPS_API_KEY, + }, + }, + // manifest: { + // application: { + // metaData: [ + // { + // "android:name": "com.google.firebase.messaging.default_notification_color", + // "android:resource": "@color/notification_icon_color", + // "tools:replace": "android:resource", + // }, + // ], + // }, + // }, }, - ], - "expo-router", - [ - "expo-build-properties", - { - newArchEnabled: false, - ios: { - flipper: false, - useFrameworks: "static", - }, - android: { - compileSdkVersion: 35, - targetSdkVersion: 35, - buildToolsVersion: "35.0.0", - }, + web: { + bundler: 'metro', + output: 'web-build', + favicon: './assets/images/favicon.png', }, - ], - "@react-native-firebase/app", - "@react-native-firebase/messaging", - "@react-native-google-signin/google-signin", - // Add the iOS sound plugin - [withIOSSounds], - [ - "expo-notifications", - { - sounds: ["./assets/sounds/notification.aiff"], + plugins: [ + withNotificationToolsReplace, + [ + 'expo-camera', + { + cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera', + microphonePermission: 'Allow $(PRODUCT_NAME) to access your microphone', + recordAudioAndroid: true, + }, + ], + 'expo-router', + [ + 'expo-build-properties', + { + newArchEnabled: false, + ios: { + flipper: false, + useFrameworks: 'static', + }, + android: { + compileSdkVersion: 35, + targetSdkVersion: 35, + buildToolsVersion: '35.0.0', + }, + }, + ], + '@react-native-firebase/app', + '@react-native-firebase/messaging', + '@react-native-google-signin/google-signin', + // Add the iOS sound plugin + [withIOSSounds], + [ + 'expo-notifications', + { + sounds: ['./assets/sounds/notification.aiff'], + }, + ], + ], + experiments: { + typedRoutes: true, + reactCanary: true, }, - ], - ], - experiments: { - typedRoutes: true, - reactCanary: true, }, - }, }; From 0da65b40a09fc2a4809285ed610f08640bf5ad46 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:32:11 -0500 Subject: [PATCH 11/26] Added oauth web client id. --- GoogleService-Info.plist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist index b014b2e..7fb687e 100644 --- a/GoogleService-Info.plist +++ b/GoogleService-Info.plist @@ -24,6 +24,10 @@ IS_SIGNIN_ENABLED + CLIENT_ID + 906784991953-aoq1tgiac6ge9aqv4kguai26265393h9.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.906784991953-aoq1tgiac6ge9aqv4kguai26265393h9 GOOGLE_APP_ID 1:982053776531:ios:eec4ee1692d95d2ce3b166 From 2872fc6b5582b93eabb8af4e3f908aef85c7dec1 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:32:22 -0500 Subject: [PATCH 12/26] Added oauth config props. --- google-services.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/google-services.json b/google-services.json index 7497b6b..5ad11ed 100644 --- a/google-services.json +++ b/google-services.json @@ -12,7 +12,12 @@ "package_name": "com.digitalnomad91.codebuilderadmin" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "906784991953-aoq1tgiac6ge9aqv4kguai26265393h9.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDdDNSdl2ukCEpTy2tZ2IHz1lD3UiftQFo" @@ -20,7 +25,12 @@ ], "services": { "appinvite_service": { - "other_platform_oauth_client": [] + "other_platform_oauth_client": [ + { + "client_id": "906784991953-aoq1tgiac6ge9aqv4kguai26265393h9.apps.googleusercontent.com", + "client_type": 3 + } + ] } } } From c93bee60dc6425e480185dec34a544d32e0472a7 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:32:44 -0500 Subject: [PATCH 13/26] Created notifier service to handle 'internal' app notifications. --- services/notifier.service.ts | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 services/notifier.service.ts diff --git a/services/notifier.service.ts b/services/notifier.service.ts new file mode 100644 index 0000000..481ecbe --- /dev/null +++ b/services/notifier.service.ts @@ -0,0 +1,62 @@ +// A minimal pub/sub notifier for in-app banners (non-blocking) +// Allows triggering notifications from anywhere (including non-React files) + +export type NotifierType = 'info' | 'success' | 'warning' | 'error'; + +export type NotifyPayload = { + id?: string; + type?: NotifierType; + title?: string; + message: string; + duration?: number; // ms +}; + +type InternalNotify = Required> & { id: string }; + +type Subscriber = (n: InternalNotify) => void; + +const subscribers = new Set(); +const defaultDuration = 5000; +const recentMap = new Map(); +const dedupeWindowMs = 2000; // ignore duplicates in this window + +export function subscribe(sub: Subscriber) { + subscribers.add(sub); + return () => subscribers.delete(sub); +} + +export function notify(payload: NotifyPayload): string | null { + const id = payload.id || `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + const type = payload.type ?? 'error'; + const title = payload.title ?? (type === 'error' ? 'Unexpected Error' : 'Notice'); + const message = payload.message; + const duration = payload.duration ?? defaultDuration; + + if (!message) return null; + + const key = `${type}|${title}|${message}`; + const now = Date.now(); + const last = recentMap.get(key) ?? 0; + if (now - last < dedupeWindowMs) return null; // drop duplicate spam + recentMap.set(key, now); + + const data: InternalNotify = { id, type, title, message, duration }; + subscribers.forEach((s) => s(data)); + return id; +} + +export function notifyError(err: unknown, fallback = 'An unexpected error occurred.') { + const message = normalizeError(err) || fallback; + return notify({ type: 'error', title: 'Unexpected Error', message }); +} + +function normalizeError(err: unknown): string | null { + if (!err) return null; + if (err instanceof Error) return err.message || String(err); + if (typeof err === 'string') return err; + try { + return JSON.stringify(err); + } catch { + return String(err); + } +} From ea155959216b2ec1afad59afaf97549d8953f932 Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:33:27 -0500 Subject: [PATCH 14/26] Added global error handler + other important stuffs. --- app/_layout.tsx | 220 +++++++++++++++++++++++------------------------- 1 file changed, 105 insertions(+), 115 deletions(-) diff --git a/app/_layout.tsx b/app/_layout.tsx index ec22df6..04c6fe8 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,134 +1,124 @@ -import { useEffect } from "react"; -import { useNavigationContainerRef } from "@react-navigation/native"; -import { useReactNavigationDevTools } from "@dev-plugins/react-navigation"; -import { SplashScreen } from "expo-router"; -import { - usePushNotifications, - useNotificationObserver, -} from "@/hooks/usePushNotifications"; -import { useFonts } from "expo-font"; -import FontAwesome from "@expo/vector-icons/FontAwesome"; -import { - DarkTheme, - DefaultTheme, - ThemeProvider, -} from "@react-navigation/native"; -import { Stack } from "expo-router"; -import "react-native-reanimated"; -import { useColorScheme } from "@/hooks/useColorScheme"; -import { registerBackgroundFetch } from "@/utils/tasks.utils"; -import ErrorBoundary from "@/components/ErrorBoundary"; -import { AuthProvider, useAuth } from "@/hooks/useAuth"; - -import { - setJSExceptionHandler, - setNativeExceptionHandler, -} from "react-native-exception-handler"; -import { reportError, safeReport } from "@/services/errorReporting.service"; -import { showFatalErrorNotification } from "@/utils/notifications.utils"; -import { setupGlobalErrorHandlers } from "../utils/globalErrorhandler"; +// app/_layout.tsx +import { useEffect } from 'react'; +import { useNavigationContainerRef } from '@react-navigation/native'; +import { useReactNavigationDevTools } from '@dev-plugins/react-navigation'; +import { SplashScreen, Stack } from 'expo-router'; +import 'react-native-reanimated'; + +import { useFonts } from 'expo-font'; +import FontAwesome from '@expo/vector-icons/FontAwesome'; +import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; + +import { usePushNotifications, useNotificationObserver } from '@/hooks/usePushNotifications'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { registerBackgroundFetch } from '@/utils/tasks.utils'; +import ErrorBoundary from '@/components/ErrorBoundary'; +import { NotificationProvider } from '@/providers/NotificationProvider'; + +// βœ… import from providers + hooks (don’t import AuthProvider from hooks) +import { AuthProvider } from '@/providers/AuthProvider'; +import { useAuth } from '@/hooks/useAuth'; + +import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler'; +import { safeReport } from '@/services/errorReporting.service'; +import { showFatalErrorNotification } from '@/utils/notifications.utils'; +// βœ… keep pathing consistent with your alias +import { setupGlobalErrorHandlers } from '@/utils/globalErrorhandler'; + +// Prevent auto-hiding the splash until fonts are loaded +SplashScreen.preventAutoHideAsync().catch(() => { + /* no-op */ +}); // --- Global Exception Handler Setup --- - const jsExceptionHandler = (maybeError: unknown, isFatal: boolean) => { - const error = - maybeError instanceof Error - ? maybeError - : new Error( - typeof maybeError === "string" - ? maybeError - : JSON.stringify(maybeError) || "Unknown non-Error thrown" - ); - - console.log("Caught JS Exception:", error, isFatal); - - // Use safeReport to avoid throwing from inside handler - safeReport(error, { - isFatal, - errorInfo: { componentStack: "jsExceptionHandler" }, - }); - - if (isFatal) { - showFatalErrorNotification(error); - } + const error = maybeError instanceof Error ? maybeError : new Error(typeof maybeError === 'string' ? maybeError : JSON.stringify(maybeError) || 'Unknown non-Error thrown'); + + console.log('Caught JS Exception:', error, isFatal); + + safeReport(error, { + isFatal, + errorInfo: { componentStack: 'jsExceptionHandler' }, + }); + + if (isFatal) showFatalErrorNotification(error); }; const nativeExceptionHandler = (exceptionString: string) => { - console.log("Caught Native Exception:", exceptionString); - const error = new Error(`Native Exception: ${exceptionString}`); + console.log('Caught Native Exception:', exceptionString); + const error = new Error(`Native Exception: ${exceptionString}`); - safeReport(error, { - isFatal: true, - errorInfo: { componentStack: "nativeExceptionHandler" }, - }); + safeReport(error, { + isFatal: true, + errorInfo: { componentStack: 'nativeExceptionHandler' }, + }); - showFatalErrorNotification(error); + showFatalErrorNotification(error); }; setJSExceptionHandler(jsExceptionHandler, true); setNativeExceptionHandler(nativeExceptionHandler); // --- Component Definition --- - export default function RootLayout() { - const navigationRef = useNavigationContainerRef(); - - // Enable push notifications and observer - usePushNotifications(); - useNotificationObserver(); - - // Register background fetch task - useEffect(() => { - registerBackgroundFetch(); - }, []); - - // Enable React Navigation DevTools - useReactNavigationDevTools(navigationRef); - - // Load fonts - const [loaded] = useFonts({ - SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"), - ...FontAwesome.font, - }); - - // Hide the splash screen once fonts are loaded - useEffect(() => { - if (loaded) { - SplashScreen.hideAsync(); - } - }, [loaded]); - - if (!loaded) { - return null; - } - - return ( - - - - ); + const navigationRef = useNavigationContainerRef(); + + // Enable push notifications and observer + usePushNotifications(); + useNotificationObserver(); + + // Register background fetch task + useEffect(() => { + registerBackgroundFetch(); + }, []); + + // React Navigation DevTools + useReactNavigationDevTools(navigationRef); + + // Load fonts + const [loaded] = useFonts({ + SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), + ...FontAwesome.font, + }); + + // Hide the splash screen once fonts are loaded + useEffect(() => { + if (loaded) { + SplashScreen.hideAsync().catch(() => {}); + } + }, [loaded]); + + if (!loaded) return null; + + return ( + + + + ); } function RootLayoutNav() { - const colorScheme = useColorScheme(); - const { user } = useAuth(); - - useEffect(() => { - setupGlobalErrorHandlers(); - }, []); - - return ( - - - - {user ? ( - - ) : ( - - )} - - - - - ); + const colorScheme = useColorScheme(); + const { user } = useAuth(); + // Avoid logging during render to prevent side-effects in LogViewer + useEffect(() => { + console.log('User: ', user); + }, [user]); + + useEffect(() => { + setupGlobalErrorHandlers(); + }, []); + + return ( + + + + + {user ? : } + + + + + + ); } From 1a97386c83bb9356839e9394e984523dbfa9e4ec Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:34:03 -0500 Subject: [PATCH 15/26] Updated tabs menu. --- app/(tabs)/_layout.tsx | 130 ++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 73 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 96ff612..677eb4e 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,80 +1,64 @@ -import FontAwesome from "@expo/vector-icons/FontAwesome"; -import { Link, Tabs } from "expo-router"; -import { Pressable, Image, StyleSheet } from "react-native"; +import FontAwesome from '@expo/vector-icons/FontAwesome'; +import { Link, Tabs } from 'expo-router'; +import { Pressable, Image, StyleSheet } from 'react-native'; -import Colors from "@/constants/Colors"; -import { useColorScheme } from "@/hooks/useColorScheme"; -import { useClientOnlyValue } from "@/hooks/useClientOnlyValue"; +import Colors from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { useClientOnlyValue } from '@/hooks/useClientOnlyValue'; // You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ -function TabBarIcon(props: { - name: React.ComponentProps["name"]; - color: string; -}) { - return ; +function TabBarIcon(props: { name: React.ComponentProps['name']; color: string }) { + return ; } export default function TabLayout() { - const colorScheme = useColorScheme(); - return ( - - ( - - ), - headerRight: () => ( - - - {({ pressed }) => ( - - )} - - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - - ); + const colorScheme = useColorScheme(); + return ( + + , + headerRight: () => ( + + + {({ pressed }) => ( + + )} + + + ), + }} + /> + , + }} + /> + , + }} + /> + , + }} + /> + + ); } From 89dba29c52dbc13dab0579317432bb8c59bd81ce Mon Sep 17 00:00:00 2001 From: digitalnomad91 Date: Sun, 17 Aug 2025 22:34:32 -0500 Subject: [PATCH 16/26] Added battery info button, login button, and other testing stuff to main page. --- app/(tabs)/index.tsx | 243 +++++++++++++++++++++---------------------- 1 file changed, 118 insertions(+), 125 deletions(-) diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 25e4a13..7e9b748 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,142 +1,135 @@ -import { - View, - Text, - Button, - ActivityIndicator, - StyleSheet, - Image, - ScrollView, -} from "react-native"; -import MapView, { Marker } from "react-native-maps"; -import { useLocation } from "@/hooks/useLocation"; -import LogViewer from "@/components/LogViewer"; -import BatteryInfo from "@/components/BatteryInfo"; -import { triggerLocalSampleNotification } from "@/utils/notifications.utils"; -import { usePushNotifications } from "@/hooks/usePushNotifications"; +import { View, Text, Button, ActivityIndicator, StyleSheet, Image, ScrollView } from 'react-native'; +import MapView, { Marker } from 'react-native-maps'; +import { Link } from 'expo-router'; +import { useLocation } from '@/hooks/useLocation'; +import LogViewer from '@/components/LogViewer'; +import BatteryInfo from '@/components/BatteryInfo'; +import { triggerLocalSampleNotification } from '@/utils/notifications.utils'; +import { usePushNotifications } from '@/hooks/usePushNotifications'; export default function LocationComponent() { - const { fcmToken } = usePushNotifications(); - const { location, address, error, loading, fetchLocation } = - useLocation(fcmToken); + const { fcmToken } = usePushNotifications(); + const { location, address, error, loading, fetchLocation } = useLocation(fcmToken); - const textColor = "#ffffff"; + const textColor = '#ffffff'; - return ( - - - + return ( + + + - + - {/* Battery information */} - + {/* Battery information */} + - {/* Location / Map */} - {loading ? ( - - ) : error ? ( - {error} - ) : location && address ? ( - <> - - Address: {address.name}, {address.city}, {address.region},{" "} - {address.country} - - - {location.coords.latitude} - {location.coords.longitude} - + {/* Location / Map */} + {loading ? ( + + ) : error ? ( + {error} + ) : location && address ? ( + <> + + Address: {address.name}, {address.city}, {address.region}, {address.country} + + + {location.coords.latitude} - {location.coords.longitude} + - - - {location && ( - + + + {location && ( + + )} + + + + ) : ( + Waiting for location... )} - - - - ) : ( - - Waiting for location... - - )} -