From e4bc0d3896b171f85c761d25289b0e31a610d157 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 6 Sep 2025 17:50:04 +0530 Subject: [PATCH] added OTA support --- .gitignore | 2 + App.tsx | 13 +- android/app/src/main/AndroidManifest.xml | 4 +- android/app/src/main/res/values/strings.xml | 1 + app.json | 17 +- build-and-publish-app-release.sh | 61 +++ package-lock.json | 535 +++++++++++--------- package.json | 20 +- src/screens/SettingsScreen.tsx | 417 ++++++++++++++- src/services/updateService.ts | 467 +++++++++++++++++ xavia-ota | 1 + 11 files changed, 1279 insertions(+), 259 deletions(-) create mode 100644 build-and-publish-app-release.sh create mode 100644 src/services/updateService.ts create mode 160000 xavia-ota diff --git a/.gitignore b/.gitignore index b913d15..4f446fd 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ local-scrapers-repo worki.json VERSION_UPDATE_README.md hackintosh-emulator-fix.sh +/ota-builds +src/screens/xavio.md diff --git a/App.tsx b/App.tsx index a2130bd..134250a 100644 --- a/App.tsx +++ b/App.tsx @@ -29,6 +29,7 @@ import { TrailerProvider } from './src/contexts/TrailerContext'; import SplashScreen from './src/components/SplashScreen'; import AsyncStorage from '@react-native-async-storage/async-storage'; import * as Sentry from '@sentry/react-native'; +import UpdateService from './src/services/updateService'; Sentry.init({ dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992', @@ -60,20 +61,24 @@ const ThemedApp = () => { const [isAppReady, setIsAppReady] = useState(false); const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState(null); - // Check onboarding status + // Check onboarding status and initialize update service useEffect(() => { - const checkOnboardingStatus = async () => { + const initializeApp = async () => { try { + // Check onboarding status const onboardingCompleted = await AsyncStorage.getItem('hasCompletedOnboarding'); setHasCompletedOnboarding(onboardingCompleted === 'true'); + + // Initialize update service + await UpdateService.initialize(); } catch (error) { - console.error('Error checking onboarding status:', error); + console.error('Error initializing app:', error); // Default to showing onboarding if we can't check setHasCompletedOnboarding(false); } }; - checkOnboardingStatus(); + initializeApp(); }, []); // Create custom themes based on current theme diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3f9784b..e85b4c8 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -13,9 +13,11 @@ - + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index a5cdeec..fdd8657 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,4 +3,5 @@ contain false dark + 0.6.0-beta.8 \ No newline at end of file diff --git a/app.json b/app.json index 485a043..3c27ad1 100644 --- a/app.json +++ b/app.json @@ -72,7 +72,20 @@ "organization": "tapframe" } ], - "expo-localization" - ] + "expo-localization", + [ + "expo-updates", + { + "username": "nayifleo" + } + ] + ], + "updates": { + "enabled": true, + "checkAutomatically": "ON_LOAD", + "fallbackToCacheTimeout": 0, + "url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest" + }, + "runtimeVersion": "0.6.0-beta.8" } } diff --git a/build-and-publish-app-release.sh b/build-and-publish-app-release.sh new file mode 100644 index 0000000..e17ceae --- /dev/null +++ b/build-and-publish-app-release.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Check if the correct number of arguments are provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Get the current commit hash and message +commitHash=$(git rev-parse HEAD) +commitMessage=$(git log -1 --pretty=%B) + +# Assign arguments to variables +runtimeVersion=$1 +serverHost=$2 + +# Generate a timestamp for the output folder +timestamp=$(date -u +%Y%m%d%H%M%S) +outputFolder="ota-builds/$timestamp" + +# Ask the user to confirm the hash, commit message, runtime version, and output folder +echo "Output Folder: $outputFolder" +echo "Runtime Version: $runtimeVersion" +echo "Commit Hash: $commitHash" +echo "Commit Message: $commitMessage" + +read -p "Do you want to proceed with these values? (y/n): " confirm + +if [ "$confirm" != "y" ]; then + echo "Operation cancelled by the user." + exit 1 +fi + +rm -rf $outputFolder +mkdir -p $outputFolder + +# Run expo export with the specified output folder +npx expo export --output-dir $outputFolder + +# Extract expo config property from app.json and save to expoconfig.json +jq '.expo' app.json > $outputFolder/expoconfig.json + + +# Zip the output folder +cd $outputFolder +zip -q -r ${timestamp}.zip . + + +# Upload the zip file to the server +curl -X POST $serverHost/api/upload -F "file=@${timestamp}.zip" -F "runtimeVersion=$runtimeVersion" -F "commitHash=$commitHash" -F "commitMessage=$commitMessage" + +echo "" + +echo "Uploaded to $serverHost/api/upload" +cd .. + +# Remove the output folder and zip file +rm -rf $outputFolder + +echo "Removed $outputFolder" +echo "Done" diff --git a/package-lock.json b/package-lock.json index d6e8b48..f2a023a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,28 @@ { "name": "nuvio", - "version": "1.0.0", + "version": "0.6.0-beta.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nuvio", - "version": "1.0.0", + "version": "0.6.0-beta.6", "dependencies": { "@backpackapp-io/react-native-toast": "^0.14.0", "@expo/metro-runtime": "~4.0.1", - "@expo/vector-icons": "^14.1.0", + "@expo/vector-icons": "~14.0.4", + "@lottiefiles/dotlottie-react": "^0.6.5", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/blur": "^4.4.1", "@react-native-community/netinfo": "^11.4.1", - "@react-native-community/slider": "^4.5.6", - "@react-native-picker/picker": "^2.11.0", + "@react-native-community/slider": "4.5.5", + "@react-native-picker/picker": "2.9.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "@react-navigation/stack": "^7.2.10", - "@sentry/react-native": "^6.15.1", - "@shopify/flash-list": "^2.0.2", + "@sentry/react-native": "~6.10.0", + "@shopify/flash-list": "1.7.3", "@supabase/supabase-js": "^2.54.0", "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", @@ -30,7 +31,7 @@ "cheerio-without-node-native": "^0.20.2", "date-fns": "^4.1.0", "eventemitter3": "^5.0.1", - "expo": "~52.0.43", + "expo": "~52.0.47", "expo-application": "~6.0.2", "expo-auth-session": "^6.0.3", "expo-blur": "^14.0.3", @@ -47,9 +48,10 @@ "expo-screen-orientation": "~8.0.4", "expo-status-bar": "~2.0.1", "expo-system-ui": "^4.0.9", + "expo-updates": "~0.27.4", "expo-web-browser": "~14.0.2", "lodash": "^4.17.21", - "lottie-react-native": "^7.3.1", + "lottie-react-native": "7.1.0", "posthog-react-native": "^4.4.0", "react": "18.3.1", "react-native": "0.76.9", @@ -58,10 +60,10 @@ "react-native-image-colors": "^2.5.0", "react-native-immersive-mode": "^2.0.2", "react-native-paper": "^5.13.1", - "react-native-reanimated": "^3.18.0", + "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", - "react-native-svg": "^15.11.2", + "react-native-svg": "15.8.0", "react-native-url-polyfill": "^2.0.0", "react-native-video": "^6.12.0", "react-native-vlc-media-player": "^1.0.87", @@ -77,9 +79,9 @@ } }, "node_modules/@0no-co/graphql.web": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.1.2.tgz", - "integrity": "sha512-N2NGsU5FLBhT8NZ+3l2YrzZSHITjNXNuDhC4iDiikv0IujaJ0Xc6xIxQZ/Ek3Cb+rgPjnLHYyJm11tInuJn+cw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz", + "integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==", "license": "MIT", "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" @@ -2340,9 +2342,9 @@ } }, "node_modules/@expo/cli": { - "version": "0.22.24", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.24.tgz", - "integrity": "sha512-lhdenxBC8/x/vL39j79eXE09mOaqNNLmiSDdY/PblnI+UNzGgsQ48hBTYa/MQhd0ioXXVKurZL2941dLKwcxJw==", + "version": "0.22.26", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.26.tgz", + "integrity": "sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.0.8", @@ -2358,7 +2360,7 @@ "@expo/osascript": "^2.1.6", "@expo/package-manager": "^1.7.2", "@expo/plist": "^0.2.2", - "@expo/prebuild-config": "^8.0.31", + "@expo/prebuild-config": "~8.2.0", "@expo/rudder-sdk-node": "^1.1.1", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", @@ -2423,9 +2425,9 @@ } }, "node_modules/@expo/cli/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2729,9 +2731,9 @@ } }, "node_modules/@expo/metro-config/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -2759,9 +2761,9 @@ } }, "node_modules/@expo/osascript": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.6.tgz", - "integrity": "sha512-SbMp4BUwDAKiFF4zZEJf32rRYMeNnLK9u4FaPo0lQRer60F+SKd20NTSys0wgssiVeQyQz2OhGLRx3cxYowAGw==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.2.5.tgz", + "integrity": "sha512-Bpp/n5rZ0UmpBOnl7Li3LtM7la0AR3H9NNesqL+ytW5UiqV/TbonYW3rDZY38u4u/lG7TnYflVIVQPD+iqZJ5w==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -2772,23 +2774,36 @@ } }, "node_modules/@expo/package-manager": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.7.2.tgz", - "integrity": "sha512-wT/qh9ebNjl6xr00bYkSh93b6E/78J3JPlT6WzGbxbsnv5FIZKB/nr522oWqVe1E+ML7BpXs8WugErWDN9kOFg==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.8.6.tgz", + "integrity": "sha512-gcdICLuL+nHKZagPIDC5tX8UoDDB8vNA5/+SaQEqz8D+T2C4KrEJc2Vi1gPAlDnKif834QS6YluHWyxjk0yZlQ==", "license": "MIT", "dependencies": { - "@expo/json-file": "^9.0.2", + "@expo/json-file": "^9.1.5", "@expo/spawn-async": "^1.7.2", - "ansi-regex": "^5.0.0", "chalk": "^4.0.0", - "find-up": "^5.0.0", - "js-yaml": "^3.13.1", - "micromatch": "^4.0.8", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", - "resolve-workspace-root": "^2.0.0", - "split": "^1.0.1", - "sudo-prompt": "9.1.1" + "resolve-workspace-root": "^2.0.0" + } + }, + "node_modules/@expo/package-manager/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@expo/package-manager/node_modules/@expo/json-file": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz", + "integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.3" } }, "node_modules/@expo/plist": { @@ -2803,9 +2818,9 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "8.0.31", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.31.tgz", - "integrity": "sha512-YTuS5ic9KolD/WA3GqgLcZytHQU1dpitlZ/cbDq8ZqkY+1ae5YWX+GkYEZf2VyECPaWnHYuDGddaTQVw5miTRg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.2.0.tgz", + "integrity": "sha512-CxiPpd980s0jyxi7eyN3i/7YKu3XL+8qPjBZUCYtc0+axpGweqIkq2CslyLSKHyqVyH/zlPkbVgWdyiYavFS5Q==", "license": "MIT", "dependencies": { "@expo/config": "~10.0.11", @@ -2837,9 +2852,9 @@ } }, "node_modules/@expo/prebuild-config/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -2849,9 +2864,9 @@ } }, "node_modules/@expo/prebuild-config/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2912,14 +2927,12 @@ "license": "MIT" }, "node_modules/@expo/vector-icons": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.1.0.tgz", - "integrity": "sha512-7T09UE9h8QDTsUeMGymB4i+iqvtEeaO5VvUjryFB4tugDTG/bkzViWA74hm5pfjjDEhYMXWaX112mcvhccmIwQ==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.4.tgz", + "integrity": "sha512-+yKshcbpDfbV4zoXOgHxCwh7lkE9VVTT5T03OUlBsqfze1PLy6Hi4jp1vSb1GVbY6eskvMIivGVc9SKzIv0oEQ==", "license": "MIT", - "peerDependencies": { - "expo-font": "*", - "react": "*", - "react-native": "*" + "dependencies": { + "prop-types": "^15.8.1" } }, "node_modules/@expo/ws-tunnel": { @@ -3414,6 +3427,26 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lottiefiles/dotlottie-react": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-react/-/dotlottie-react-0.6.5.tgz", + "integrity": "sha512-4jbye+HKHaiKwai4+bcAwJfMwW0L55cAGIN0ZKCz+EqXs1R3YhB08VYeRu2LwDUybThgFQnl/XkB4loCfA7t2A==", + "license": "MIT", + "dependencies": { + "@lottiefiles/dotlottie-web": "0.25.0", + "debounce": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@lottiefiles/dotlottie-web": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-web/-/dotlottie-web-0.25.0.tgz", + "integrity": "sha512-k+4tTckmy7y319qY4AlZiLm4dP95KLXfZd1l1jbss0qX/eyV4KKT+4iLuBfhoFSljfXmM15Oma+e8d663CHPyA==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3462,9 +3495,9 @@ } }, "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3521,9 +3554,9 @@ } }, "node_modules/@react-native-community/slider": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.6.tgz", - "integrity": "sha512-UhLPFeqx0YfPLrEz8ffT3uqAyXWu6iqFjohNsbp4cOU7hnJwg2RXtDnYHoHMr7MOkZDVdlLMdrSrAuzY6KGqrg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.5.tgz", + "integrity": "sha512-x2N415pg4ZxIltArOKczPwn7JEYh+1OxQ4+hTnafomnMsqs65HZuEWcX+Ch8c5r8V83DiunuQUf5hWGWlw8hQQ==", "license": "MIT" }, "node_modules/@react-native-masked-view/masked-view": { @@ -3538,13 +3571,10 @@ } }, "node_modules/@react-native-picker/picker": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.0.tgz", - "integrity": "sha512-QuZU6gbxmOID5zZgd/H90NgBnbJ3VV6qVzp6c7/dDrmWdX8S0X5YFYgDcQFjE3dRen9wB9FWnj2VVdPU64adSg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.9.0.tgz", + "integrity": "sha512-khEhIW/uhfMqq/+tvg4rEAiPGT8GX+Y6QydlP2TSMSmRHoSJK+ShXvXZXSr4Sii4imkj4BwvLunGywwtQDODqg==", "license": "MIT", - "workspaces": [ - "example" - ], "peerDependencies": { "react": "*", "react-native": "*" @@ -4156,9 +4186,9 @@ } }, "node_modules/@sentry/babel-plugin-component-annotate": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz", - "integrity": "sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.2.tgz", + "integrity": "sha512-D+SKQ266ra/wo87s9+UI/rKQi3qhGPCR8eSCDe0VJudhjHsqyNU+JJ5lnIGCgmZaWFTXgdBP/gdr1Iz1zqGs4Q==", "license": "MIT", "engines": { "node": ">= 14" @@ -4181,9 +4211,9 @@ } }, "node_modules/@sentry/cli": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.46.0.tgz", - "integrity": "sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.4.tgz", + "integrity": "sha512-BoSZDAWJiz/40tu6LuMDkSgwk4xTsq6zwqYoUqLU3vKBR/VsaaQGvu6EWxZXORthfZU2/5Agz0+t220cge6VQw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -4200,20 +4230,19 @@ "node": ">= 10" }, "optionalDependencies": { - "@sentry/cli-darwin": "2.46.0", - "@sentry/cli-linux-arm": "2.46.0", - "@sentry/cli-linux-arm64": "2.46.0", - "@sentry/cli-linux-i686": "2.46.0", - "@sentry/cli-linux-x64": "2.46.0", - "@sentry/cli-win32-arm64": "2.46.0", - "@sentry/cli-win32-i686": "2.46.0", - "@sentry/cli-win32-x64": "2.46.0" + "@sentry/cli-darwin": "2.42.4", + "@sentry/cli-linux-arm": "2.42.4", + "@sentry/cli-linux-arm64": "2.42.4", + "@sentry/cli-linux-i686": "2.42.4", + "@sentry/cli-linux-x64": "2.42.4", + "@sentry/cli-win32-i686": "2.42.4", + "@sentry/cli-win32-x64": "2.42.4" } }, "node_modules/@sentry/cli-darwin": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz", - "integrity": "sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.4.tgz", + "integrity": "sha512-PZV4Y97VDWBR4rIt0HkJfXaBXlebIN2s/FDzC3iHINZE5OG62CDFsnC4/lbGlf2/UZLDaGGIK7mYwSHhTvN+HQ==", "license": "BSD-3-Clause", "optional": true, "os": [ @@ -4224,9 +4253,9 @@ } }, "node_modules/@sentry/cli-linux-arm": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz", - "integrity": "sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.4.tgz", + "integrity": "sha512-lBn0oeeg62h68/4Eo6zbPq99Idz5t0VRV48rEU/WKeM4MtQCvG/iGGQ3lBFW2yNiUBzXZIK9poXLEcgbwmcRVw==", "cpu": [ "arm" ], @@ -4234,17 +4263,16 @@ "optional": true, "os": [ "linux", - "freebsd", - "android" + "freebsd" ], "engines": { "node": ">=10" } }, "node_modules/@sentry/cli-linux-arm64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz", - "integrity": "sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.4.tgz", + "integrity": "sha512-Ex8vRnryyzC/9e43daEmEqPS+9uirY/l6Hw2lAvhBblFaL7PTWNx52H+8GnYGd9Zy2H3rWNyBDYfHwnErg38zA==", "cpu": [ "arm64" ], @@ -4252,17 +4280,16 @@ "optional": true, "os": [ "linux", - "freebsd", - "android" + "freebsd" ], "engines": { "node": ">=10" } }, "node_modules/@sentry/cli-linux-i686": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz", - "integrity": "sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.4.tgz", + "integrity": "sha512-IBJg0aHjsLCL4LvcFa3cXIjA+4t5kPqBT9y+PoDu4goIFxYD8zl7mbUdGJutvJafTk8Akf4ss4JJXQBjg019zA==", "cpu": [ "x86", "ia32" @@ -4271,17 +4298,16 @@ "optional": true, "os": [ "linux", - "freebsd", - "android" + "freebsd" ], "engines": { "node": ">=10" } }, "node_modules/@sentry/cli-linux-x64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz", - "integrity": "sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.4.tgz", + "integrity": "sha512-gXI5OEiOSNiAEz7VCE6AZcAgHJ47mlgal3+NmbE8XcHmFOnyDws9FNie6PJAy8KZjXi3nqoBP9JVAbnmOix3uA==", "cpu": [ "x64" ], @@ -4289,33 +4315,16 @@ "optional": true, "os": [ "linux", - "freebsd", - "android" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/cli-win32-arm64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz", - "integrity": "sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==", - "cpu": [ - "arm64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "win32" + "freebsd" ], "engines": { "node": ">=10" } }, "node_modules/@sentry/cli-win32-i686": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz", - "integrity": "sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.4.tgz", + "integrity": "sha512-vZuR3UPHKqOMniyrijrrsNwn9usaRysXq78F6WV0cL0ZyPLAmY+KBnTDSFk1Oig2pURnzaTm+RtcZu2fc8mlzg==", "cpu": [ "x86", "ia32" @@ -4330,9 +4339,9 @@ } }, "node_modules/@sentry/cli-win32-x64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz", - "integrity": "sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==", + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.4.tgz", + "integrity": "sha512-OIBj3uaQ6nAERSm5Dcf8UIhyElEEwMNsZEEppQpN4IKl0mrwb/57AznM23Dvpu6GR8WGbVQUSolt879YZR5E9g==", "cpu": [ "x64" ], @@ -4345,31 +4354,6 @@ "node": ">=10" } }, - "node_modules/@sentry/cli/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@sentry/cli/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@sentry/core": { "version": "8.54.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", @@ -4397,14 +4381,14 @@ } }, "node_modules/@sentry/react-native": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-6.15.1.tgz", - "integrity": "sha512-uNYjkhi7LUeXe+a3ui3N+sUZ4PbBh/P3Q6Pz5esOQOAEV1N7hxkdnHVic1cVHsirEQvy9rUJPBnja47Va7OpQA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-6.10.0.tgz", + "integrity": "sha512-B56vc+pnFHMiu3cabFb454v4qD0zObW6JVzJ5Gb6fIMdt93AFIJg10ZErzC+ump7xM4BOEROFFRuLiyvadvlPA==", "license": "MIT", "dependencies": { - "@sentry/babel-plugin-component-annotate": "3.5.0", + "@sentry/babel-plugin-component-annotate": "3.2.2", "@sentry/browser": "8.54.0", - "@sentry/cli": "2.46.0", + "@sentry/cli": "2.42.4", "@sentry/core": "8.54.0", "@sentry/react": "8.54.0", "@sentry/types": "8.54.0", @@ -4449,11 +4433,12 @@ } }, "node_modules/@shopify/flash-list": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.0.2.tgz", - "integrity": "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.7.3.tgz", + "integrity": "sha512-RLhNptm02aqpqZvjj9pJPcU+EVYxOAJhPRCmDOaUbUP86+636w+plsbjpBPSYGvPZhPj56RtZ9FBlvolPeEmYA==", "license": "MIT", "dependencies": { + "recyclerlistview": "4.2.1", "tslib": "2.8.1" }, "peerDependencies": { @@ -5214,22 +5199,22 @@ "license": "MIT" }, "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": { @@ -6769,16 +6754,16 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -7070,6 +7055,18 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -7422,9 +7419,9 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -7705,18 +7702,18 @@ "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" }, "node_modules/expo": { - "version": "52.0.44", - "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.44.tgz", - "integrity": "sha512-qj3+MWxmqLyBaYQ8jDOvVLEgSqNplH3cf+nDhxCo4C1cpTPD1u/HGh1foibtaeuCYLHsE5km1lrcOpRbFJ4luQ==", + "version": "52.0.47", + "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.47.tgz", + "integrity": "sha512-Mkvl7Qi2k+V3FdNRUD+yDj8GqU4IiYulLfl36BmSZs8lh/kCYPhTiyBLiEGPfz7d25QKbPWG727ESozbkbvatw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.22.24", + "@expo/cli": "0.22.26", "@expo/config": "~10.0.11", "@expo/config-plugins": "~9.0.17", "@expo/fingerprint": "0.11.11", "@expo/metro-config": "0.19.12", - "@expo/vector-icons": "^14.0.0", + "@expo/vector-icons": "~14.0.4", "babel-preset-expo": "~12.0.11", "expo-asset": "~11.0.5", "expo-constants": "~17.0.8", @@ -7924,6 +7921,12 @@ "node": "*" } }, + "node_modules/expo-eas-client": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.13.3.tgz", + "integrity": "sha512-t+1F1tiDocSot8iSnrn/CjTUMvVvPV2DpafSVcticpbSzMGybEN7wcamO1t18fK7WxGXpZE9gxtd80qwv/LLqQ==", + "license": "MIT" + }, "node_modules/expo-file-system": { "version": "18.0.12", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.12.tgz", @@ -8169,6 +8172,12 @@ "react-native": "*" } }, + "node_modules/expo-structured-headers": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-4.0.0.tgz", + "integrity": "sha512-uPiwZjWq3AdFGgY52+I2nGPrNa6izxAglymPXHUZLekZW290GqIUOk7MBNDD4sg4JwUbSi3gdxEurpEvuq+FSg==", + "license": "MIT" + }, "node_modules/expo-system-ui": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-4.0.9.tgz", @@ -8195,6 +8204,35 @@ "integrity": "sha512-FRjRvs7RgsXjkbGSOjYSxhX5V70c0IzA/jy3HXeYpATMwD9fOR1DbveLW497QGsVdCa0vThbJUtR8rIzAfpHQA==", "license": "MIT" }, + "node_modules/expo-updates": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.27.4.tgz", + "integrity": "sha512-0rg4L2fFPEjTR/qnZ9Te4Q4irVC8uvNcTZW1pWnWbadG1SLv2PKjS1MYX5BboKzC3ao0H7m++5TP3hWhNg9org==", + "license": "MIT", + "dependencies": { + "@expo/code-signing-certificates": "0.0.5", + "@expo/config": "~10.0.11", + "@expo/config-plugins": "~9.0.17", + "@expo/spawn-async": "^1.7.2", + "arg": "4.1.0", + "chalk": "^4.1.2", + "expo-eas-client": "~0.13.3", + "expo-manifests": "~0.15.7", + "expo-structured-headers": "~4.0.0", + "expo-updates-interface": "~1.0.0", + "fast-glob": "^3.3.2", + "fbemitter": "^3.0.0", + "ignore": "^5.3.1", + "resolve-from": "^5.0.0" + }, + "bin": { + "expo-updates": "bin/cli.js" + }, + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, "node_modules/expo-updates-interface": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz", @@ -8204,6 +8242,12 @@ "expo": "*" } }, + "node_modules/expo-updates/node_modules/arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "license": "MIT" + }, "node_modules/expo-web-browser": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-14.0.2.tgz", @@ -8524,14 +8568,15 @@ } }, "node_modules/form-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz", - "integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.35" }, "engines": { @@ -9094,6 +9139,31 @@ "npm": ">=1.3.7" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -10497,9 +10567,9 @@ } }, "node_modules/lottie-react-native": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.3.1.tgz", - "integrity": "sha512-VW0gtiP1i3Jv6+PaVaNg6QDIqAXwAqmxVp1UOJFqsB6FeBN0zc8M/XR8DE8YiayZzd149nlqGOgmwoV7FbRGDw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.1.0.tgz", + "integrity": "sha512-73jtQySxRZ8KTTSKf6CtcpCt8tpOCw4NRiCST4HTYgXlycxIihIp89jRcK8rS/QiBKl5bzyixMzpVmd4mYVH5Q==", "license": "Apache-2.0", "peerDependencies": { "@lottiefiles/dotlottie-react": "^0.6.5", @@ -11406,9 +11476,9 @@ } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11558,9 +11628,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12357,9 +12427,9 @@ } }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -12502,7 +12572,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "optional": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0", @@ -12517,7 +12586,6 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", - "optional": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -12659,16 +12727,6 @@ "react-native": ">=0.60.5" } }, - "node_modules/react-native-is-edge-to-edge": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz", - "integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-paper": { "version": "5.13.1", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.13.1.tgz", @@ -12721,9 +12779,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.18.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.18.0.tgz", - "integrity": "sha512-eVcNcqeOkMW+BUWAHdtvN3FKgC8J8wiEJkX6bNGGQaLS7m7e4amTfjIcqf/Ta+lerZLurmDaQ0lICI1CKPrb1Q==", + "version": "3.16.7", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.7.tgz", + "integrity": "sha512-qoUUQOwE1pHlmQ9cXTJ2MX9FQ9eHllopCLiWOkDkp6CER95ZWeXhJCP4cSm6AD4jigL5jHcZf/SkWrg8ttZUsw==", "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", @@ -12736,8 +12794,7 @@ "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", - "invariant": "^2.2.4", - "react-native-is-edge-to-edge": "1.1.7" + "invariant": "^2.2.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0", @@ -12779,9 +12836,9 @@ } }, "node_modules/react-native-svg": { - "version": "15.11.2", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz", - "integrity": "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw==", + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.8.0.tgz", + "integrity": "sha512-KHJzKpgOjwj1qeZzsBjxNdoIgv2zNCO9fVcoq2TEhTRsVV5DGTZ9JzUZwybd7q4giT/H3RdtqC3u44dWdO0Ffw==", "license": "MIT", "dependencies": { "css-select": "^5.1.0", @@ -13266,6 +13323,21 @@ "node": ">=0.10.0" } }, + "node_modules/recyclerlistview": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.1.tgz", + "integrity": "sha512-NtVYjofwgUCt1rEsTp6jHQg/47TWjnO92TU2kTVgJ9wsc/ely4HnizHHa+f/dI7qaw4+zcSogElrLjhMltN2/g==", + "license": "Apache-2.0", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -14062,18 +14134,6 @@ "node": ">=0.10.0" } }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", @@ -14394,13 +14454,6 @@ "node": ">= 6" } }, - "node_modules/sudo-prompt": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.1.1.tgz", - "integrity": "sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14767,12 +14820,6 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "license": "MIT" }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" - }, "node_modules/timm": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", @@ -14874,6 +14921,12 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==", + "license": "ISC" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -14972,9 +15025,9 @@ } }, "node_modules/undici": { - "version": "6.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", - "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", "engines": { "node": ">=18.17" diff --git a/package.json b/package.json index cdc738f..c848304 100644 --- a/package.json +++ b/package.json @@ -11,18 +11,19 @@ "dependencies": { "@backpackapp-io/react-native-toast": "^0.14.0", "@expo/metro-runtime": "~4.0.1", - "@expo/vector-icons": "^14.1.0", + "@expo/vector-icons": "~14.0.4", + "@lottiefiles/dotlottie-react": "^0.6.5", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/blur": "^4.4.1", "@react-native-community/netinfo": "^11.4.1", - "@react-native-community/slider": "^4.5.6", - "@react-native-picker/picker": "^2.11.0", + "@react-native-community/slider": "4.5.5", + "@react-native-picker/picker": "2.9.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "@react-navigation/stack": "^7.2.10", - "@sentry/react-native": "^6.15.1", - "@shopify/flash-list": "^2.0.2", + "@sentry/react-native": "~6.10.0", + "@shopify/flash-list": "1.7.3", "@supabase/supabase-js": "^2.54.0", "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", @@ -31,7 +32,7 @@ "cheerio-without-node-native": "^0.20.2", "date-fns": "^4.1.0", "eventemitter3": "^5.0.1", - "expo": "~52.0.43", + "expo": "~52.0.47", "expo-application": "~6.0.2", "expo-auth-session": "^6.0.3", "expo-blur": "^14.0.3", @@ -48,9 +49,10 @@ "expo-screen-orientation": "~8.0.4", "expo-status-bar": "~2.0.1", "expo-system-ui": "^4.0.9", + "expo-updates": "~0.27.4", "expo-web-browser": "~14.0.2", "lodash": "^4.17.21", - "lottie-react-native": "^7.3.1", + "lottie-react-native": "7.1.0", "posthog-react-native": "^4.4.0", "react": "18.3.1", "react-native": "0.76.9", @@ -59,10 +61,10 @@ "react-native-image-colors": "^2.5.0", "react-native-immersive-mode": "^2.0.2", "react-native-paper": "^5.13.1", - "react-native-reanimated": "^3.18.0", + "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", - "react-native-svg": "^15.11.2", + "react-native-svg": "15.8.0", "react-native-url-polyfill": "^2.0.0", "react-native-video": "^6.12.0", "react-native-vlc-media-player": "^1.0.87", diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 6978334..2f1f7aa 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -13,7 +13,8 @@ import { Dimensions, Image, Button, - Linking + Linking, + Clipboard } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useNavigation } from '@react-navigation/native'; @@ -30,6 +31,7 @@ import { useAccount } from '../contexts/AccountContext'; import { catalogService } from '../services/catalogService'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Sentry from '@sentry/react-native'; +import UpdateService from '../services/updateService'; const { width, height } = Dimensions.get('window'); const isTablet = width >= 768; @@ -43,6 +45,7 @@ const SETTINGS_CATEGORIES = [ { id: 'appearance', title: 'Appearance', icon: 'palette' }, { id: 'integrations', title: 'Integrations', icon: 'extension' }, { id: 'playback', title: 'Playback', icon: 'play-circle-outline' }, + { id: 'updates', title: 'Updates', icon: 'system-update' }, { id: 'about', title: 'About', icon: 'info-outline' }, { id: 'developer', title: 'Developer', icon: 'code' }, { id: 'cache', title: 'Cache', icon: 'cached' }, @@ -220,6 +223,348 @@ const Sidebar: React.FC = ({ selectedCategory, onCategorySelect, c ); }; +// Updates Section Component +interface UpdatesSectionProps { + isTablet: boolean; +} + +const UpdatesSection: React.FC = ({ isTablet }) => { + const { currentTheme } = useTheme(); + const [updateInfo, setUpdateInfo] = useState(null); + const [isChecking, setIsChecking] = useState(false); + const [isInstalling, setIsInstalling] = useState(false); + const [lastChecked, setLastChecked] = useState(null); + const [logs, setLogs] = useState([]); + const [showLogs, setShowLogs] = useState(false); + const [lastOperation, setLastOperation] = useState(''); + + const checkForUpdates = async () => { + try { + setIsChecking(true); + setLastOperation('Checking for updates...'); + + const info = await UpdateService.checkForUpdates(); + setUpdateInfo(info); + setLastChecked(new Date()); + + // Refresh logs after operation + const logs = UpdateService.getLogs(); + setLogs(logs); + + if (info.isAvailable) { + setLastOperation(`Update available: ${info.manifest?.id?.substring(0, 8) || 'unknown'}...`); + } else { + setLastOperation('No updates available'); + } + } catch (error) { + console.error('Error checking for updates:', error); + setLastOperation(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`); + Alert.alert('Error', 'Failed to check for updates'); + } finally { + setIsChecking(false); + } + }; + + const installUpdate = async () => { + try { + setIsInstalling(true); + setLastOperation('Installing update...'); + + const success = await UpdateService.downloadAndInstallUpdate(); + + // Refresh logs after operation + const logs = UpdateService.getLogs(); + setLogs(logs); + + if (success) { + setLastOperation('Update installed successfully'); + Alert.alert('Success', 'Update will be applied on next app restart'); + } else { + setLastOperation('No update available to install'); + Alert.alert('No Update', 'No update available to install'); + } + } catch (error) { + console.error('Error installing update:', error); + setLastOperation(`Installation error: ${error instanceof Error ? error.message : 'Unknown error'}`); + Alert.alert('Error', 'Failed to install update'); + } finally { + setIsInstalling(false); + } + }; + + const getCurrentUpdateInfo = async () => { + const info = await UpdateService.getCurrentUpdateInfo(); + setUpdateInfo(info); + const logs = UpdateService.getLogs(); + setLogs(logs); + }; + + const refreshLogs = () => { + const logs = UpdateService.getLogs(); + setLogs(logs); + }; + + const clearLogs = () => { + UpdateService.clearLogs(); + setLogs([]); + setLastOperation('Logs cleared'); + }; + + const copyLog = (logText: string) => { + Clipboard.setString(logText); + Alert.alert('Copied', 'Log entry copied to clipboard'); + }; + + const copyAllLogs = () => { + const allLogsText = logs.join('\n'); + Clipboard.setString(allLogsText); + Alert.alert('Copied', 'All logs copied to clipboard'); + }; + + const addTestLog = () => { + UpdateService.addTestLog(`Test log entry at ${new Date().toISOString()}`); + const logs = UpdateService.getLogs(); + setLogs(logs); + setLastOperation('Test log added'); + }; + + const testConnectivity = async () => { + try { + setLastOperation('Testing connectivity...'); + const isReachable = await UpdateService.testUpdateConnectivity(); + const logs = UpdateService.getLogs(); + setLogs(logs); + + if (isReachable) { + setLastOperation('Update server is reachable'); + } else { + setLastOperation('Update server is not reachable'); + } + } catch (error) { + console.error('Error testing connectivity:', error); + setLastOperation(`Connectivity test error: ${error instanceof Error ? error.message : 'Unknown error'}`); + const logs = UpdateService.getLogs(); + setLogs(logs); + } + }; + + const testAssetUrls = async () => { + try { + setLastOperation('Testing asset URLs...'); + await UpdateService.testAllAssetUrls(); + const logs = UpdateService.getLogs(); + setLogs(logs); + setLastOperation('Asset URL testing completed'); + } catch (error) { + console.error('Error testing asset URLs:', error); + setLastOperation(`Asset URL test error: ${error instanceof Error ? error.message : 'Unknown error'}`); + const logs = UpdateService.getLogs(); + setLogs(logs); + } + }; + + // Load current update info on mount + useEffect(() => { + const loadInitialData = async () => { + await getCurrentUpdateInfo(); + // Also refresh logs to ensure we have the latest + refreshLogs(); + }; + loadInitialData(); + }, []); + + const formatDate = (date: Date) => { + return date.toLocaleString(); + }; + + return ( + + + ( + + {isChecking && ( + + )} + + + )} + isTablet={isTablet} + /> + + {updateInfo?.isAvailable && ( + ( + + {isInstalling && ( + + )} + + + )} + isTablet={isTablet} + /> + )} + + + + {lastChecked && ( + + )} + + setShowLogs(!showLogs)} + renderControl={() => ( + + + + )} + isTablet={isTablet} + /> + + + {showLogs && ( + + + + + Update Service Logs + + + + + + + + + + + + + + + + + + + + + + + + + {logs.length === 0 ? ( + + No logs available + + ) : ( + logs.map((log, index) => { + const isError = log.includes('[ERROR]'); + const isWarning = log.includes('[WARN]'); + + return ( + copyLog(log)} + activeOpacity={0.7} + > + + + {log} + + + + + ); + }) + )} + + + + )} + + ); +}; + const SettingsScreen: React.FC = () => { const { settings, updateSetting } = useSettings(); const navigation = useNavigation>(); @@ -582,7 +927,7 @@ const SettingsScreen: React.FC = () => { /> { ) : null; + case 'updates': + return ; + default: return null; } @@ -748,6 +1096,7 @@ const SettingsScreen: React.FC = () => { {renderCategoryContent('appearance')} {renderCategoryContent('integrations')} {renderCategoryContent('playback')} + {renderCategoryContent('updates')} {renderCategoryContent('about')} {renderCategoryContent('developer')} {renderCategoryContent('cache')} @@ -1048,6 +1397,70 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: '500', }, + loadingSpinner: { + width: 16, + height: 16, + borderWidth: 2, + borderRadius: 8, + borderTopColor: 'transparent', + marginRight: 8, + }, + // Logs styles + logsContainer: { + padding: 16, + }, + logsHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 12, + }, + logsHeaderText: { + fontSize: 16, + fontWeight: '600', + }, + logsActions: { + flexDirection: 'row', + gap: 8, + }, + logActionButton: { + width: 32, + height: 32, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + logsScrollView: { + maxHeight: 200, + borderRadius: 8, + padding: 12, + }, + logEntry: { + marginBottom: 4, + paddingVertical: 4, + paddingHorizontal: 8, + borderRadius: 4, + }, + logEntryContent: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + logText: { + fontSize: 12, + fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + lineHeight: 16, + flex: 1, + marginRight: 8, + }, + logCopyIcon: { + opacity: 0.6, + }, + noLogsText: { + fontSize: 14, + textAlign: 'center', + paddingVertical: 20, + }, }); export default SettingsScreen; \ No newline at end of file diff --git a/src/services/updateService.ts b/src/services/updateService.ts new file mode 100644 index 0000000..1cedbba --- /dev/null +++ b/src/services/updateService.ts @@ -0,0 +1,467 @@ +import * as Updates from 'expo-updates'; +import { Platform } from 'react-native'; + +export interface UpdateInfo { + isAvailable: boolean; + manifest?: Partial; + isNew?: boolean; + isEmbeddedLaunch?: boolean; +} + +export class UpdateService { + private static instance: UpdateService; + private updateCheckInterval: NodeJS.Timeout | null = null; + private readonly CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes + private logs: string[] = []; + private readonly MAX_LOGS = 100; // Keep last 100 logs + + private constructor() {} + + public static getInstance(): UpdateService { + if (!UpdateService.instance) { + UpdateService.instance = new UpdateService(); + } + return UpdateService.instance; + } + + /** + * Add a log entry with timestamp - always log to console for adb logcat visibility + */ + private addLog(message: string, level: 'INFO' | 'WARN' | 'ERROR' = 'INFO'): void { + const timestamp = new Date().toISOString(); + const logEntry = `[${timestamp}] [${level}] ${message}`; + + this.logs.unshift(logEntry); + + // Keep only the last MAX_LOGS entries + if (this.logs.length > this.MAX_LOGS) { + this.logs = this.logs.slice(0, this.MAX_LOGS); + } + + // Always log to console - this will be visible in adb logcat for production builds + // Use different console methods for better filtering in logcat + if (level === 'ERROR') { + console.error(`[UpdateService] ${logEntry}`); + } else if (level === 'WARN') { + console.warn(`[UpdateService] ${logEntry}`); + } else { + console.log(`[UpdateService] ${logEntry}`); + } + + // Also log with a consistent prefix for easy filtering + console.log(`UpdateService: ${logEntry}`); + } + + /** + * Get all logs + */ + public getLogs(): string[] { + return [...this.logs]; + } + + /** + * Clear all logs + */ + public clearLogs(): void { + this.logs = []; + this.addLog('Logs cleared', 'INFO'); + } + + /** + * Add a test log entry (useful for debugging) + */ + public addTestLog(message: string): void { + this.addLog(`TEST: ${message}`, 'INFO'); + } + + /** + * Test the update URL connectivity + */ + public async testUpdateConnectivity(): Promise { + this.addLog('Testing update server connectivity...', 'INFO'); + + try { + const updateUrl = this.getUpdateUrl(); + this.addLog(`Testing URL: ${updateUrl}`, 'INFO'); + + const response = await fetch(updateUrl, { + method: 'GET', + headers: { + 'expo-runtime-version': Updates.runtimeVersion || '0.6.0-beta.8', + 'expo-platform': Platform.OS, + 'expo-protocol-version': '1', + 'expo-api-version': '1', + }, + }); + + this.addLog(`Response status: ${response.status}`, 'INFO'); + this.addLog(`Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`, 'INFO'); + + if (response.ok) { + this.addLog('Update server is reachable', 'INFO'); + + // Try to get the response body to see what we're getting + try { + const responseText = await response.text(); + this.addLog(`Response body preview: ${responseText.substring(0, 500)}...`, 'INFO'); + } catch (bodyError) { + this.addLog(`Could not read response body: ${bodyError instanceof Error ? bodyError.message : String(bodyError)}`, 'WARN'); + } + + return true; + } else { + this.addLog(`Update server returned error: ${response.status} ${response.statusText}`, 'ERROR'); + return false; + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Connectivity test failed: ${errorMessage}`, 'ERROR'); + return false; + } + } + + /** + * Test individual asset URL accessibility + */ + public async testAssetUrl(assetUrl: string): Promise { + this.addLog(`Testing asset URL: ${assetUrl}`, 'INFO'); + + try { + const response = await fetch(assetUrl, { + method: 'HEAD', // Use HEAD to avoid downloading the full asset + }); + + this.addLog(`Asset response status: ${response.status}`, 'INFO'); + this.addLog(`Asset response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`, 'INFO'); + + if (response.ok) { + this.addLog('Asset URL is accessible', 'INFO'); + return true; + } else { + this.addLog(`Asset URL returned error: ${response.status} ${response.statusText}`, 'ERROR'); + return false; + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Asset URL test failed: ${errorMessage}`, 'ERROR'); + return false; + } + } + + /** + * Test all asset URLs from the latest update manifest + */ + public async testAllAssetUrls(): Promise { + this.addLog('Testing all asset URLs from latest update...', 'INFO'); + + try { + const update = await Updates.checkForUpdateAsync(); + + if (!update.isAvailable || !update.manifest) { + this.addLog('No update available or no manifest found', 'WARN'); + return; + } + + this.addLog(`Found update with ${update.manifest.assets?.length || 0} assets`, 'INFO'); + + if (update.manifest.assets && update.manifest.assets.length > 0) { + for (let i = 0; i < update.manifest.assets.length; i++) { + const asset = update.manifest.assets[i]; + if (asset.url) { + this.addLog(`Testing asset ${i + 1}/${update.manifest.assets.length}: ${asset.key || 'unknown'}`, 'INFO'); + const isAccessible = await this.testAssetUrl(asset.url); + if (!isAccessible) { + this.addLog(`Asset ${i + 1} is not accessible: ${asset.url}`, 'ERROR'); + } + } else { + this.addLog(`Asset ${i + 1} has no URL`, 'ERROR'); + } + } + } + + // Test launch asset (check if it exists in the manifest) + const manifest = update.manifest as any; // Type assertion to access launchAsset + if (manifest.launchAsset?.url) { + this.addLog('Testing launch asset...', 'INFO'); + const isAccessible = await this.testAssetUrl(manifest.launchAsset.url); + if (!isAccessible) { + this.addLog(`Launch asset is not accessible: ${manifest.launchAsset.url}`, 'ERROR'); + } + } else { + this.addLog('No launch asset URL found', 'ERROR'); + } + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Failed to test asset URLs: ${errorMessage}`, 'ERROR'); + } + } + + /** + * Initialize the update service + */ + public async initialize(): Promise { + this.addLog('Initializing UpdateService...', 'INFO'); + this.addLog(`Environment: ${__DEV__ ? 'Development' : 'Production'}`, 'INFO'); + this.addLog(`Platform: ${Platform.OS}`, 'INFO'); + this.addLog(`Updates enabled: ${Updates.isEnabled}`, 'INFO'); + this.addLog(`Runtime version: ${Updates.runtimeVersion || 'unknown'}`, 'INFO'); + this.addLog(`Update URL: ${this.getUpdateUrl()}`, 'INFO'); + + try { + // Always perform update check regardless of environment for debugging + this.addLog('Performing initial update check...', 'INFO'); + const updateInfo = await this.checkForUpdates(); + + if (updateInfo.isAvailable) { + this.addLog(`Update available: ${updateInfo.manifest?.id || 'unknown'}`, 'INFO'); + } else { + this.addLog('No updates available', 'INFO'); + } + + // Check if we're running in a development environment + if (__DEV__) { + this.addLog('Running in development mode, but allowing update checks for testing', 'WARN'); + this.addLog('UpdateService initialization completed (dev mode)', 'INFO'); + // Don't return early - allow update checks in dev mode for testing + } + + // Check if updates are enabled + if (!Updates.isEnabled) { + this.addLog('Updates are not enabled in this environment', 'WARN'); + this.addLog('UpdateService initialization completed (updates disabled)', 'INFO'); + return; + } + + this.addLog('Updates are enabled, setting up periodic checks', 'INFO'); + + // Set up periodic update checks + this.startPeriodicUpdateChecks(); + this.addLog('UpdateService initialization completed successfully', 'INFO'); + } catch (error) { + this.addLog(`Initialization failed: ${error instanceof Error ? error.message : String(error)}`, 'ERROR'); + console.error('Update service initialization failed:', error); + } + } + + /** + * Check for available updates + */ + public async checkForUpdates(): Promise { + this.addLog('Starting update check...', 'INFO'); + this.addLog(`Update URL: ${this.getUpdateUrl()}`, 'INFO'); + this.addLog(`Runtime version: ${Updates.runtimeVersion || 'unknown'}`, 'INFO'); + this.addLog(`Platform: ${Platform.OS}`, 'INFO'); + this.addLog(`Updates enabled: ${Updates.isEnabled}`, 'INFO'); + + try { + // Always attempt the check for debugging purposes + this.addLog('Calling Updates.checkForUpdateAsync()...', 'INFO'); + const startTime = Date.now(); + + const update = await Updates.checkForUpdateAsync(); + const duration = Date.now() - startTime; + + this.addLog(`Update check completed in ${duration}ms`, 'INFO'); + this.addLog(`Check result - isAvailable: ${update.isAvailable}`, 'INFO'); + + if (update.isAvailable) { + this.addLog(`Update available! ID: ${update.manifest?.id || 'unknown'}`, 'INFO'); + + if (update.manifest) { + this.addLog(`Manifest ID: ${update.manifest.id || 'unknown'}`, 'INFO'); + } + + // Check if we can actually install updates + if (__DEV__) { + this.addLog('WARNING: Update found but in development mode - installation will be skipped', 'WARN'); + } else if (!Updates.isEnabled) { + this.addLog('WARNING: Update found but updates disabled - installation will be skipped', 'WARN'); + } else { + this.addLog('Update found and installation is possible', 'INFO'); + } + + return { + isAvailable: true, + manifest: update.manifest, + isNew: false, // Default value since isNew is not available in the type + isEmbeddedLaunch: false // Default value since isEmbeddedLaunch is not available in the type + }; + } + + this.addLog('No updates available', 'INFO'); + return { isAvailable: false }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Update check failed: ${errorMessage}`, 'ERROR'); + console.error('Failed to check for updates:', error); + return { isAvailable: false }; + } + } + + /** + * Download and install the latest update + */ + public async downloadAndInstallUpdate(): Promise { + this.addLog('Starting update download and installation...', 'INFO'); + + try { + // Check environment and updates status first + if (__DEV__) { + this.addLog('Running in development mode - update installation may have limitations', 'WARN'); + this.addLog('In development mode, Updates.checkForUpdateAsync() may not work properly', 'WARN'); + // Don't return false - allow attempting updates in dev mode for testing + } + + if (!Updates.isEnabled) { + this.addLog('Update installation skipped (updates disabled)', 'WARN'); + this.addLog('Updates.isEnabled is false - this is why installation fails', 'ERROR'); + return false; + } + + this.addLog('Environment checks passed, proceeding with installation', 'INFO'); + this.addLog('Checking for available updates before installation...', 'INFO'); + this.addLog(`Update URL: ${this.getUpdateUrl()}`, 'INFO'); + this.addLog(`Runtime version: ${Updates.runtimeVersion || 'unknown'}`, 'INFO'); + this.addLog(`Platform: ${Platform.OS}`, 'INFO'); + + const update = await Updates.checkForUpdateAsync(); + + if (update.isAvailable) { + this.addLog(`Update found, starting download. ID: ${update.manifest?.id || 'unknown'}`, 'INFO'); + this.addLog(`Manifest details: ${JSON.stringify(update.manifest, null, 2)}`, 'INFO'); + + const downloadStartTime = Date.now(); + this.addLog('Calling Updates.fetchUpdateAsync()...', 'INFO'); + + try { + await Updates.fetchUpdateAsync(); + const downloadDuration = Date.now() - downloadStartTime; + + this.addLog(`Update downloaded successfully in ${downloadDuration}ms`, 'INFO'); + this.addLog('Calling Updates.reloadAsync() to apply update...', 'INFO'); + + await Updates.reloadAsync(); + + this.addLog('Update installation completed successfully', 'INFO'); + return true; + } catch (fetchError) { + const errorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError); + this.addLog(`Update fetch failed: ${errorMessage}`, 'ERROR'); + this.addLog(`Fetch error stack: ${fetchError instanceof Error ? fetchError.stack : 'No stack available'}`, 'ERROR'); + throw fetchError; // Re-throw to be caught by outer catch block + } + } + + this.addLog('No update available for installation', 'WARN'); + this.addLog('Updates.checkForUpdateAsync() returned isAvailable: false', 'INFO'); + return false; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Update installation failed: ${errorMessage}`, 'ERROR'); + this.addLog(`Error stack: ${error instanceof Error ? error.stack : 'No stack available'}`, 'ERROR'); + console.error('Failed to download/install update:', error); + return false; + } + } + + /** + * Get current update info + */ + public async getCurrentUpdateInfo(): Promise { + try { + this.addLog('Getting current update info...', 'INFO'); + this.addLog(`Updates.isEnabled: ${Updates.isEnabled}`, 'INFO'); + this.addLog(`Updates.isEmbeddedLaunch: ${Updates.isEmbeddedLaunch}`, 'INFO'); + + if (__DEV__) { + this.addLog('In development mode - update info may not be accurate', 'WARN'); + } + + if (!Updates.isEnabled) { + this.addLog('Updates disabled - returning false for isAvailable', 'WARN'); + return { isAvailable: false }; + } + + const info = { + isAvailable: Updates.isEmbeddedLaunch === false, + manifest: Updates.manifest, + isNew: false, // Default value since Updates.isNew is not available + isEmbeddedLaunch: Updates.isEmbeddedLaunch + }; + + this.addLog(`Current update info - Available: ${info.isAvailable}, Embedded: ${info.isEmbeddedLaunch}`, 'INFO'); + + if (info.manifest) { + this.addLog(`Current manifest ID: ${info.manifest.id || 'unknown'}`, 'INFO'); + } else { + this.addLog('No manifest available', 'INFO'); + } + + return info; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Failed to get current update info: ${errorMessage}`, 'ERROR'); + console.error('Failed to get current update info:', error); + return { isAvailable: false }; + } + } + + /** + * Start periodic update checks + */ + private startPeriodicUpdateChecks(): void { + if (this.updateCheckInterval) { + this.addLog('Stopping existing periodic update checks', 'INFO'); + clearInterval(this.updateCheckInterval); + } + + this.addLog(`Starting periodic update checks every ${this.CHECK_INTERVAL / 1000} seconds`, 'INFO'); + + this.updateCheckInterval = setInterval(async () => { + try { + this.addLog('Performing scheduled update check...', 'INFO'); + await this.checkForUpdates(); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.addLog(`Scheduled update check failed: ${errorMessage}`, 'ERROR'); + console.error('Periodic update check failed:', error); + } + }, this.CHECK_INTERVAL); + } + + /** + * Stop periodic update checks + */ + public stopPeriodicUpdateChecks(): void { + if (this.updateCheckInterval) { + this.addLog('Stopping periodic update checks', 'INFO'); + clearInterval(this.updateCheckInterval); + this.updateCheckInterval = null; + } else { + this.addLog('No periodic update checks running to stop', 'INFO'); + } + } + + /** + * Get the update URL for the current platform + */ + public getUpdateUrl(): string { + // Use the URL from app.json configuration + return 'https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest'; + } + + /** + * Cleanup resources + */ + public cleanup(): void { + this.addLog('Cleaning up UpdateService resources...', 'INFO'); + this.stopPeriodicUpdateChecks(); + this.addLog('UpdateService cleanup completed', 'INFO'); + } +} + +export default UpdateService.getInstance(); + + + diff --git a/xavia-ota b/xavia-ota new file mode 160000 index 0000000..ba2fe77 --- /dev/null +++ b/xavia-ota @@ -0,0 +1 @@ +Subproject commit ba2fe779d75a8ed285c5d37f05532c3ff4acce22