diff --git a/.gitignore b/.gitignore index 63e3246..7c21947 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ # dependencies node_modules/ -package-lock.json -yarn.lock # Expo .expo/ @@ -12,6 +10,8 @@ web-build/ expo-env.d.ts # Native +/ios/ +/android/ *.orig.* *.jks *.p8 diff --git a/App.tsx b/App.tsx index d75f31c..0a0bc25 100644 --- a/App.tsx +++ b/App.tsx @@ -1,11 +1,9 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { StatusBar } from 'expo-status-bar'; import { SafeAreaProvider } from 'react-native-safe-area-context'; -import { AppState, AppStateStatus } from 'react-native'; import { SleepProvider } from './src/context/SleepContext'; import { ThemeProvider, useTheme } from './src/context/ThemeContext'; import AppNavigator from './src/navigation/AppNavigator'; -import BackgroundActivityService from './src/services/BackgroundActivityService'; import ErrorBoundary from './src/components/ErrorBoundary'; const AppContent = () => { @@ -19,39 +17,6 @@ const AppContent = () => { }; export default function App() { - useEffect(() => { - // Handle app state changes to prevent crashes on force close - const handleAppStateChange = async (nextAppState: AppStateStatus) => { - console.log(`🔄 App state changed to: ${nextAppState}`); - - if (nextAppState === 'background' || nextAppState === 'inactive') { - try { - // Gracefully handle app going to background - console.log('📱 App going to background - preparing cleanup'); - } catch (error) { - console.error('❌ Error handling app background state:', error); - } - } - }; - - // Subscribe to app state changes - const subscription = AppState.addEventListener('change', handleAppStateChange); - - // Cleanup function to prevent memory leaks - return () => { - console.log('🧹 Cleaning up app state subscription'); - subscription?.remove(); - - // Emergency cleanup for background service - try { - const backgroundService = BackgroundActivityService.getInstance(); - backgroundService.stopMonitoring().catch(console.error); - } catch (error) { - console.error('❌ Emergency cleanup error:', error); - } - }; - }, []); - return ( @@ -63,4 +28,4 @@ export default function App() { ); -} \ No newline at end of file +} diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..d59794f --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,62 @@ +# Sleep Detector Privacy Policy + +**Effective date:** August 14, 2026 + +Sleep Detector ("the app") is provided by SimpliXio Pte. Ltd. This policy explains how the app handles information. + +## Summary + +Sleep Detector is private by design. It does not require an account and does not include advertising, analytics, cross-app tracking, or a developer-operated cloud service for sleep and wellness data. Sleep records and settings are stored on the device unless the user explicitly exports them or enables Apple Health sync. + +## Information handled by the app + +The app can store the following information locally: + +- app activity timestamps used to estimate an inactivity interval; +- sleep estimates, confirmed sleep periods, confidence details, and edits; +- optional wellness check-ins, settings, and notification preferences; and +- optional Apple Health information described below. + +This information is used only to provide the app's sleep-review, history, export, and wellness features. + +## Apple Health and Apple Watch + +Apple Health access is optional and requested only after the user enables Health integration. With permission, the app can read sleep stages and selected recovery signals, including heart rate variability, resting heart rate, respiratory rate, and wrist temperature, to provide private sleep context. When Health sync is enabled, the app writes only sleep periods the user has confirmed. + +Health information is not used for advertising, sold, shared with data brokers, or transmitted to a developer-operated server. Apple Health permissions can be changed at any time in the Health or Settings app. + +The Apple Watch companion reads permitted Apple Health information only when the user requests a refresh. It does not run a continuous workout or always-on sensor session. + +## Notifications + +If the user grants notification permission, the app can schedule local bedtime reminders and confirmed-sleep summaries. Notification preferences can be changed in the app or in iOS Settings. + +## Export and sharing + +JSON and CSV exports are created only when the user requests them. The user chooses where an exported file is shared. The selected destination's privacy policy applies after the file leaves the app. + +## Data retention and deletion + +Sleep Detector keeps app data on the device until the user clears it in the app or uninstalls the app. Information previously written to Apple Health is controlled through the Health app and is not removed automatically when Sleep Detector is uninstalled. + +## Security + +The app relies on iOS device protections and HealthKit authorization controls. No method of storage is completely risk-free, so users should keep their device and Apple ID secure. + +## Children + +Sleep Detector is a general wellness tool and is not directed to children under 13. The app does not knowingly collect children's personal information. + +## Health disclaimer + +Sleep Detector supports personal awareness and journaling. It is not a medical device and does not diagnose, treat, cure, or prevent any disease or sleep disorder. Users should consult a qualified healthcare professional for medical concerns. + +## Changes + +This policy may be updated when the app or legal requirements change. The effective date above identifies the latest revision. + +## Contact + +For privacy questions, contact [contact@simplixio.tech](mailto:contact@simplixio.tech). + +Product creator: [Pierre-Henry Soria](https://pierrehenry.dev). diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 8a6be07..0000000 --- a/android/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# OSX -# -.DS_Store - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml -*.hprof -.cxx/ - -# Bundle artifacts -*.jsbundle diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 6d597dc..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,177 +0,0 @@ -apply plugin: "com.android.application" -apply plugin: "org.jetbrains.kotlin.android" -apply plugin: "com.facebook.react" - -def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() - -/** - * This is the configuration block to customize your React Native Android app. - * By default you don't need to apply any configuration, just uncomment the lines you need. - */ -react { - entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) - reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() - hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" - codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() - - enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean() - // Use Expo CLI to bundle the app, this ensures the Metro config - // works correctly with Expo projects. - cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) - bundleCommand = "export:embed" - - /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '../..' - // root = file("../../") - // The folder where the react-native NPM package is. Default is ../../node_modules/react-native - // reactNativeDir = file("../../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen - // codegenDir = file("../../node_modules/@react-native/codegen") - - /* Variants */ - // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. - // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] - - /* Bundling */ - // A list containing the node command and its flags. Default is just 'node'. - // nodeExecutableAndArgs = ["node"] - - // - // The path to the CLI configuration file. Default is empty. - // bundleConfig = file(../rn-cli.config.js) - // - // The name of the generated asset file containing your JS bundle - // bundleAssetName = "MyApplication.android.bundle" - // - // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' - // entryFile = file("../js/MyApplication.android.js") - // - // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle - // extraPackagerArgs = [] - - /* Hermes Commands */ - // The hermes compiler command to run. By default it is 'hermesc' - // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" - // - // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" - // hermesFlags = ["-O", "-output-source-map"] - - /* Autolinking */ - autolinkLibrariesWithApp() -} - -/** - * Set this to true to Run Proguard on Release builds to minify the Java bytecode. - */ -def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() - -/** - * The preferred build flavor of JavaScriptCore (JSC) - * - * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` - * - * The international variant includes ICU i18n library and necessary data - * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that - * this variant is about 6MiB larger per architecture than default. - */ -def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' - -android { - ndkVersion rootProject.ext.ndkVersion - - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdk rootProject.ext.compileSdkVersion - - namespace 'com.sleepdetector.app' - defaultConfig { - applicationId 'com.sleepdetector.app' - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 19 - versionName "1.0.7" - } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' - } - } - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. - signingConfig signingConfigs.debug - shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) - } - } - packagingOptions { - jniLibs { - useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) - } - } - androidResources { - ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~' - } -} - -// Apply static values from `gradle.properties` to the `android.packagingOptions` -// Accepts values in comma delimited lists, example: -// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini -["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> - // Split option: 'foo,bar' -> ['foo', 'bar'] - def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); - // Trim all elements in place. - for (i in 0.. 0) { - println "android.packagingOptions.$prop += $options ($options.length)" - // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' - options.each { - android.packagingOptions[prop] += it - } - } -} - -dependencies { - // The version of react-native is set by the React Native Gradle Plugin - implementation("com.facebook.react:react-android") - - def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; - def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; - def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; - - if (isGifEnabled) { - // For animated gif support - implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}") - } - - if (isWebpEnabled) { - // For webp support - implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}") - if (isWebpAnimatedEnabled) { - // Animated webp support - implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}") - } - } - - if (hermesEnabled.toBoolean()) { - implementation("com.facebook.react:hermes-android") - } else { - implementation jscFlavor - } -} diff --git a/android/app/debug.keystore b/android/app/debug.keystore deleted file mode 100644 index 364e105..0000000 Binary files a/android/app/debug.keystore and /dev/null differ diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro deleted file mode 100644 index 551eb41..0000000 --- a/android/app/proguard-rules.pro +++ /dev/null @@ -1,14 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# react-native-reanimated --keep class com.swmansion.reanimated.** { *; } --keep class com.facebook.react.turbomodule.** { *; } - -# Add any project specific keep options here: diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 3ec2507..0000000 --- a/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 4d361a8..0000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/java/com/sleepdetector/app/MainActivity.kt b/android/app/src/main/java/com/sleepdetector/app/MainActivity.kt deleted file mode 100644 index 4fb8d2a..0000000 --- a/android/app/src/main/java/com/sleepdetector/app/MainActivity.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.sleepdetector.app - -import android.os.Build -import android.os.Bundle - -import com.facebook.react.ReactActivity -import com.facebook.react.ReactActivityDelegate -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled -import com.facebook.react.defaults.DefaultReactActivityDelegate - -import expo.modules.ReactActivityDelegateWrapper - -class MainActivity : ReactActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - // Set the theme to AppTheme BEFORE onCreate to support - // coloring the background, status bar, and navigation bar. - // This is required for expo-splash-screen. - setTheme(R.style.AppTheme); - super.onCreate(null) - } - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - override fun getMainComponentName(): String = "main" - - /** - * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] - * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] - */ - override fun createReactActivityDelegate(): ReactActivityDelegate { - return ReactActivityDelegateWrapper( - this, - BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, - object : DefaultReactActivityDelegate( - this, - mainComponentName, - fabricEnabled - ){}) - } - - /** - * Align the back button behavior with Android S - * where moving root activities to background instead of finishing activities. - * @see onBackPressed - */ - override fun invokeDefaultOnBackPressed() { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { - if (!moveTaskToBack(false)) { - // For non-root activities, use the default implementation to finish them. - super.invokeDefaultOnBackPressed() - } - return - } - - // Use the default back button implementation on Android S - // because it's doing more than [Activity.moveTaskToBack] in fact. - super.invokeDefaultOnBackPressed() - } -} diff --git a/android/app/src/main/java/com/sleepdetector/app/MainApplication.kt b/android/app/src/main/java/com/sleepdetector/app/MainApplication.kt deleted file mode 100644 index 4aa94a5..0000000 --- a/android/app/src/main/java/com/sleepdetector/app/MainApplication.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.sleepdetector.app - -import android.app.Application -import android.content.res.Configuration - -import com.facebook.react.PackageList -import com.facebook.react.ReactApplication -import com.facebook.react.ReactNativeHost -import com.facebook.react.ReactPackage -import com.facebook.react.ReactHost -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load -import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.soloader.OpenSourceMergedSoMapping -import com.facebook.soloader.SoLoader - -import expo.modules.ApplicationLifecycleDispatcher -import expo.modules.ReactNativeHostWrapper - -class MainApplication : Application(), ReactApplication { - - override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( - this, - object : DefaultReactNativeHost(this) { - override fun getPackages(): List { - val packages = PackageList(this).packages - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(MyReactNativePackage()) - return packages - } - - override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" - - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED - } - ) - - override val reactHost: ReactHost - get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) - - override fun onCreate() { - super.onCreate() - SoLoader.init(this, OpenSourceMergedSoMapping) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } - ApplicationLifecycleDispatcher.onApplicationCreate(this) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) - } -} diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 883b2a0..0000000 --- a/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml deleted file mode 100644 index 5c25e72..0000000 --- a/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 3941bea..0000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 3941bea..0000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index 6c05afa..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp deleted file mode 100644 index 12728c5..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index 4150c79..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index a6c805c..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp deleted file mode 100644 index 0e50e30..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 69cb6d2..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 27cf61b..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp deleted file mode 100644 index c229952..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index ba04f03..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 0645d9c..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 67e4553..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 982bfdb..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index 8ca66cc..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 6d9ee04..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 3bb623c..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml deleted file mode 100644 index 3c05de5..0000000 --- a/android/app/src/main/res/values-night/colors.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml deleted file mode 100644 index f387b90..0000000 --- a/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - #ffffff - #ffffff - #023c69 - #ffffff - \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 11d2da3..0000000 --- a/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Sleep Detector - contain - false - \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 00ab510..0000000 --- a/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index fa7b11e..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath('com.android.tools.build:gradle') - classpath('com.facebook.react:react-native-gradle-plugin') - classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') - } -} - -def reactNativeAndroidDir = new File( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('react-native/package.json')") - }.standardOutput.asText.get().trim(), - "../android" -) - -allprojects { - repositories { - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url(reactNativeAndroidDir) - } - - google() - mavenCentral() - maven { url 'https://www.jitpack.io' } - } -} - -apply plugin: "expo-root-project" -apply plugin: "com.facebook.react.rootproject" diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 9f8da22..0000000 --- a/android/gradle.properties +++ /dev/null @@ -1,59 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true - -# Enable AAPT2 PNG crunching -android.enablePngCrunchInReleaseBuilds=true - -# Use this property to specify which architecture you want to build. -# You can also override it from the CLI using -# ./gradlew -PreactNativeArchitectures=x86_64 -reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 - -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# your application. You should enable this flag either if you want -# to write custom TurboModules/Fabric components OR use libraries that -# are providing them. -newArchEnabled=true - -# Use this property to enable or disable the Hermes JS engine. -# If set to false, you will be using JSC instead. -hermesEnabled=true - -# Enable GIF support in React Native images (~200 B increase) -expo.gif.enabled=true -# Enable webp support in React Native images (~85 KB increase) -expo.webp.enabled=true -# Enable animated webp support (~3.4 MB increase) -# Disabled by default because iOS doesn't support animated webp -expo.webp.animated=false - -# Enable network inspector -EX_DEV_CLIENT_NETWORK_INSPECTOR=true - -# Use legacy packaging to compress native libraries in the resulting APK. -expo.useLegacyPackaging=false - -# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin -expo.edgeToEdgeEnabled=false \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9..0000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 37f853b..0000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index f3b75f3..0000000 --- a/android/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index 9b42019..0000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 4afb9b0..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,39 +0,0 @@ -pluginManagement { - def reactNativeGradlePlugin = new File( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })") - }.standardOutput.asText.get().trim() - ).getParentFile().absolutePath - includeBuild(reactNativeGradlePlugin) - - def expoPluginsPath = new File( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })") - }.standardOutput.asText.get().trim(), - "../android/expo-gradle-plugin" - ).absolutePath - includeBuild(expoPluginsPath) -} - -plugins { - id("com.facebook.react.settings") - id("expo-autolinking-settings") -} - -extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> - if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { - ex.autolinkLibrariesFromCommand() - } else { - ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand) - } -} -expoAutolinking.useExpoModules() - -rootProject.name = 'Sleep Detector' - -expoAutolinking.useExpoVersionCatalog() - -include ':app' -includeBuild(expoAutolinking.reactNativeGradlePlugin) diff --git a/app-store-assets/metadata/en-US/description.txt b/app-store-assets/metadata/en-US/description.txt new file mode 100644 index 0000000..104c7fd --- /dev/null +++ b/app-store-assets/metadata/en-US/description.txt @@ -0,0 +1,34 @@ +Understand when you may have slept—even if you forgot to start a timer. + +Sleep Detector notices how long the app has been away. When you return after a long interval, it creates an editable sleep estimate for review. App-away time alone is not treated as proof of sleep: low-confidence estimates always wait for your confirmation. + +For more context, optionally connect Apple Health and the Apple Watch companion. Sleep stages and selected recovery signals can refine a candidate; only periods you confirm are written back to Health when sync is enabled. + +REVIEW BEFORE SAVING + +• Inspect estimated start and end times +• See confidence and evidence details +• Edit, confirm, or dismiss each estimate +• Review history, trends, and daily wellness context + +APPLE WATCH + HEALTH + +• View last night's duration and stages on your wrist +• Refresh on demand—no continuous sensor workout +• Read Health data only after permission +• Write only confirmed sleep periods +• Avoid duplicate Health entries + +PRIVATE BY DESIGN + +• No account +• No advertising or tracking +• No cloud sleep profile +• On-device storage and analysis +• User-initiated JSON and CSV export + +BATTERY CONSCIOUS + +Sleep Detector records lightweight app lifecycle timestamps instead of running a continuous background sensor session. iOS may suspend apps; the estimate is completed when Sleep Detector is reopened. + +Sleep Detector supports awareness and journaling. It is not a medical device and does not diagnose sleep disorders. Results depend on available evidence and user confirmation. diff --git a/app-store-assets/metadata/en-US/keywords.txt b/app-store-assets/metadata/en-US/keywords.txt new file mode 100644 index 0000000..6896a1a --- /dev/null +++ b/app-store-assets/metadata/en-US/keywords.txt @@ -0,0 +1 @@ +sleep tracker,nap,Apple Health,Apple Watch,sleep journal,sleep stages,wellness,inactivity diff --git a/app-store-assets/metadata/en-US/promotional_text.txt b/app-store-assets/metadata/en-US/promotional_text.txt new file mode 100644 index 0000000..5301b25 --- /dev/null +++ b/app-store-assets/metadata/en-US/promotional_text.txt @@ -0,0 +1 @@ +Review editable sleep estimates from app inactivity, refine them with optional Apple Health and Watch evidence, and keep your data on device. diff --git a/app-store-assets/metadata/en-US/review_notes.txt b/app-store-assets/metadata/en-US/review_notes.txt new file mode 100644 index 0000000..e024315 --- /dev/null +++ b/app-store-assets/metadata/en-US/review_notes.txt @@ -0,0 +1,14 @@ +Sleep Detector does not run a continuous background sensor session. It records lightweight app lifecycle timestamps and creates an editable estimate after the app returns from an inactivity interval. + +Suggested review flow: +1. Open Settings and set the inactivity threshold to 5 minutes. +2. Put the app in the background for more than 5 minutes. +3. Reopen the app. A candidate is shown for review and can be edited, confirmed, or dismissed. + +App-only inactivity is low-confidence evidence and always requires confirmation. If Apple Health or Apple Watch sleep samples overlap the interval, they can refine its boundaries and confidence. + +Apple Health is optional. To test it, open Settings > Health Integration and enable the switch. The permission sheet requests sleep stages plus optional recovery signals. Only confirmed sleep periods are written, and only when Health sync is enabled. + +The Apple Watch companion refreshes Health data on demand and does not start a workout or continuous sensor session. If the test account has no Health sleep samples, the Watch app shows an empty state. + +No account or login is required. The app contains no ads or tracking, keeps its sleep data on device, and is not a medical device. diff --git a/app-store-assets/release-notes/1.4.1.md b/app-store-assets/release-notes/1.4.1.md new file mode 100644 index 0000000..9054d9f --- /dev/null +++ b/app-store-assets/release-notes/1.4.1.md @@ -0,0 +1,11 @@ +# What’s New in Sleep Detector 1.4.1 + +Sleep detection is now more dependable, more transparent, and easier to review. + +- Sleep Detector now completes an inactivity estimate when you return to the app, including after iOS suspends or restarts it. +- New Apple Watch support brings Apple Health sleep stages and optional recovery signals into your private daily insights. +- Confirmed sleep periods can sync to Apple Health, with duplicate protection and clear permission controls. +- Every automatic estimate includes confidence details and remains editable before you save it. +- Improved lifecycle timing, Health sync retry behavior, battery efficiency, and overall stability. + +Your sleep and wellness data stays private and is processed on your devices. diff --git a/app-store-assets/watch/README.md b/app-store-assets/watch/README.md new file mode 100644 index 0000000..23d885c --- /dev/null +++ b/app-store-assets/watch/README.md @@ -0,0 +1,26 @@ +# Apple Watch App Store screenshots + +This set contains five English Apple Watch screenshots at **422 × 514 px**, the accepted Apple Watch Ultra 3 size documented by Apple. Every final PNG is opaque sRGB without an alpha channel. + +The UI uses fictional sample data and depicts only shipped behavior: + +1. Latest recorded sleep duration and stage summary. +2. Core, Deep, and REM stages read from Apple Health. +3. Optional HRV and resting-heart-rate wellness context, explicitly non-diagnostic. +4. User-initiated Apple Health permission. +5. On-demand refresh without a continuous workout or always-on sensor loop. + +Regenerate the set with: + +```sh +./app-store-assets/watch/generate-watch-screenshots.sh +``` + +The generated background was produced with the built-in image-generation tool using this prompt: + +> Create a luxurious abstract portrait background inspired by restful sleep, with deep midnight navy flowing into royal indigo and violet, a soft cyan aurora ribbon, subtle concentric sleep-wave arcs, and tiny diffused stars. Keep darker space at the bottom and clear negative space at the top. Background only: no device, UI, copy, logos, icons, people, or watermark. + +Apple references: + +- [Screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications) +- [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) diff --git a/app-store-assets/watch/backgrounds/midnight-aurora-source.png b/app-store-assets/watch/backgrounds/midnight-aurora-source.png new file mode 100644 index 0000000..f14fcd7 Binary files /dev/null and b/app-store-assets/watch/backgrounds/midnight-aurora-source.png differ diff --git a/app-store-assets/watch/final/01-your-night-at-a-glance.png b/app-store-assets/watch/final/01-your-night-at-a-glance.png new file mode 100644 index 0000000..6406a23 Binary files /dev/null and b/app-store-assets/watch/final/01-your-night-at-a-glance.png differ diff --git a/app-store-assets/watch/final/02-every-sleep-stage.png b/app-store-assets/watch/final/02-every-sleep-stage.png new file mode 100644 index 0000000..9248328 Binary files /dev/null and b/app-store-assets/watch/final/02-every-sleep-stage.png differ diff --git a/app-store-assets/watch/final/03-recovery-in-context.png b/app-store-assets/watch/final/03-recovery-in-context.png new file mode 100644 index 0000000..d3b5e74 Binary files /dev/null and b/app-store-assets/watch/final/03-recovery-in-context.png differ diff --git a/app-store-assets/watch/final/04-health-access-is-your-choice.png b/app-store-assets/watch/final/04-health-access-is-your-choice.png new file mode 100644 index 0000000..5e50e1e Binary files /dev/null and b/app-store-assets/watch/final/04-health-access-is-your-choice.png differ diff --git a/app-store-assets/watch/final/05-smarter-data-lighter-battery.png b/app-store-assets/watch/final/05-smarter-data-lighter-battery.png new file mode 100644 index 0000000..da9b475 Binary files /dev/null and b/app-store-assets/watch/final/05-smarter-data-lighter-battery.png differ diff --git a/app-store-assets/watch/generate-watch-screenshots.sh b/app-store-assets/watch/generate-watch-screenshots.sh new file mode 100755 index 0000000..2ef29ef --- /dev/null +++ b/app-store-assets/watch/generate-watch-screenshots.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && pwd)" +SOURCE="$ROOT/backgrounds/midnight-aurora-source.png" +OVERLAYS="$ROOT/overlays" +FINAL="$ROOT/final" +WORK="$(mktemp -d "${TMPDIR:-/tmp}/sleep-watch-assets.XXXXXX")" + +trap 'rm -rf "$WORK"' EXIT + +mkdir -p "$FINAL" + +render() { + local overlay="$1" + local output="$2" + local gravity="$3" + local brightness="$4" + local saturation="$5" + local hue="$6" + local rendered_overlay="$WORK/${overlay%.svg}.png" + + rsvg-convert -w 1688 -h 2056 -o "$rendered_overlay" "$OVERLAYS/$overlay" + + magick \ + \( "$SOURCE" -resize '1688x2056^' -gravity "$gravity" -extent 1688x2056 -modulate "$brightness,$saturation,$hue" \) \ + "$rendered_overlay" \ + -compose over -composite \ + -resize '422x514!' \ + -alpha off -colorspace sRGB -type TrueColor -strip \ + "$FINAL/$output" +} + +render 01-night-glance.svg 01-your-night-at-a-glance.png north 84 108 100 +render 02-sleep-stages.svg 02-every-sleep-stage.png center 78 118 108 +render 03-recovery-context.svg 03-recovery-in-context.png south 72 108 92 +render 04-health-choice.svg 04-health-access-is-your-choice.png center 76 125 114 +render 05-battery-conscious.svg 05-smarter-data-lighter-battery.png north 72 112 88 + +magick "$FINAL"/*.png +append "$ROOT/watch-screenshot-contact-sheet.png" + +echo "Generated Apple Watch screenshots in $FINAL" diff --git a/app-store-assets/watch/overlays/01-night-glance.svg b/app-store-assets/watch/overlays/01-night-glance.svg new file mode 100644 index 0000000..200bd21 --- /dev/null +++ b/app-store-assets/watch/overlays/01-night-glance.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Your night, at a glance + A clear summary, right on your wrist. + + + + + + + + + + + + Sleep Detector + + 7h 42m + Last recorded sleep + + + + + + Core4h 31m + Deep1h 18m + REM1h 53m + + + Refresh + Fictional sample data + + diff --git a/app-store-assets/watch/overlays/02-sleep-stages.svg b/app-store-assets/watch/overlays/02-sleep-stages.svg new file mode 100644 index 0000000..be15259 --- /dev/null +++ b/app-store-assets/watch/overlays/02-sleep-stages.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + Every sleep stage, clearly + Core, Deep and REM from Apple Health. + + + + LAST NIGHT + 7h 42m + + + + + + Core4h 31m + Deep1h 18m + REM1h 53m + + Fictional sample data + + diff --git a/app-store-assets/watch/overlays/03-recovery-context.svg b/app-store-assets/watch/overlays/03-recovery-context.svg new file mode 100644 index 0000000..2603c20 --- /dev/null +++ b/app-store-assets/watch/overlays/03-recovery-context.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + Recovery, in contextWellness signals—never a diagnosis. + + + Recovery signals + From your recorded sleep window + + HRV + 54 ms + + + + Resting heart rate + 58 bpm + + Recovery signals are wellness + observations, not a stress or + anxiety diagnosis. + Fictional sample data + + diff --git a/app-store-assets/watch/overlays/04-health-choice.svg b/app-store-assets/watch/overlays/04-health-choice.svg new file mode 100644 index 0000000..7e52995 --- /dev/null +++ b/app-store-assets/watch/overlays/04-health-choice.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + Health access is your choiceConnect only when you decide. + + + + + Connect Apple Health + Use Apple Watch sleep stages for + more precise, private reports. + + Allow Health Access + Optional. You stay in control. + + diff --git a/app-store-assets/watch/overlays/05-battery-conscious.svg b/app-store-assets/watch/overlays/05-battery-conscious.svg new file mode 100644 index 0000000..6d33311 --- /dev/null +++ b/app-store-assets/watch/overlays/05-battery-conscious.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + Smarter data. Lighter battery.Refreshes on demand—never a workout. + + + Sleep Detector + + + + Last refresh + Just now + + Battery-conscious by design + No continuous workout + No always-on sensor loop + + Refresh sleep data + Reads Apple Health only when requested + + diff --git a/app-store-assets/watch/watch-screenshot-contact-sheet.png b/app-store-assets/watch/watch-screenshot-contact-sheet.png new file mode 100644 index 0000000..fed5751 Binary files /dev/null and b/app-store-assets/watch/watch-screenshot-contact-sheet.png differ diff --git a/app.json b/app.json index 24ddba1..6d9e7d1 100644 --- a/app.json +++ b/app.json @@ -2,10 +2,11 @@ "expo": { "name": "Sleep Detector", "slug": "detect-sleep", - "version": "1.1.3", + "description": "A privacy-first sleep companion conceived and built by Pierre-Henry Soria, with systems in development since early 2024.", + "version": "1.4.1", "orientation": "portrait", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", + "userInterfaceStyle": "automatic", "platforms": [ "ios", "android" @@ -20,26 +21,25 @@ ], "ios": { "supportsTablet": false, + "appleTeamId": "2V8LZ2444Y", "bundleIdentifier": "com.sleepdetector.app", - "buildNumber": "23", - "jsEngine": "jsc", "infoPlist": { - "BGTaskSchedulerPermittedIdentifiers": [ - "com.sleepdetector.app.sleeprefresh", - "com.sleepdetector.app.sleepprocessing", - "background-activity-monitor" - ], - "NSMotionUsageDescription": "We use motion data to more accurately detect when you're asleep or awake by monitoring device movement patterns", - "NSHealthShareUsageDescription": "We use Health data to improve sleep detection accuracy and to compare with our detection algorithm", - "NSHealthUpdateUsageDescription": "We can write your detected sleep data to Apple Health to consolidate your health information", - "NSUserNotificationsUsageDescription": "We send you notifications when sleep is detected and when you wake up with sleep summaries", + "NSHumanReadableCopyright": "© 2024–2026 Pierre-Henry Soria", + "NSHealthShareUsageDescription": "When you enable Health integration, Sleep Detector reads sleep stages and optional recovery signals such as heart rate variability, resting heart rate, respiratory rate, and wrist temperature to improve your private daily report.", + "NSHealthUpdateUsageDescription": "Sleep Detector writes only the sleep periods you confirm to Apple Health when you enable Health sync.", + "NSUserNotificationsUsageDescription": "Sleep Detector sends bedtime reminders and optional summaries after you confirm a sleep period.", "UISupportedInterfaceOrientations": [ "UIInterfaceOrientationPortrait" - ], - "UISupportedInterfaceOrientations~ipad": null + ] }, "config": { "usesNonExemptEncryption": false + }, + "entitlements": { + "com.apple.developer.healthkit": true, + "com.apple.security.application-groups": [ + "group.com.sleepdetector.app" + ] } }, "android": { @@ -48,42 +48,32 @@ "backgroundColor": "#ffffff" }, "package": "com.sleepdetector.app", - "versionCode": 22, + "versionCode": 23, "permissions": [ - "android.permission.ACTIVITY_RECOGNITION", - "android.permission.WAKE_LOCK", - "android.permission.RECEIVE_BOOT_COMPLETED", - "android.permission.FOREGROUND_SERVICE", - "android.permission.VIBRATE", - "android.permission.USE_FULL_SCREEN_INTENT" + "android.permission.VIBRATE" ], - "config": { - "googleMobileAdsAppId": "ca-app-pub-3940256099942544~3347511713" - }, "blockedPermissions": [ "android.permission.READ_PHONE_STATE", "android.permission.READ_CONTACTS" ] }, "plugins": [ - "expo-task-manager", - "expo-background-fetch", [ - "expo-notifications", + "@kingstinct/react-native-healthkit", { - "icon": "./assets/notification-icon.png", - "color": "#2196F3" + "NSHealthShareUsageDescription": "When you enable Health integration, Sleep Detector reads sleep stages and optional recovery signals such as heart rate variability, resting heart rate, respiratory rate, and wrist temperature to improve your private daily report.", + "NSHealthUpdateUsageDescription": "Sleep Detector writes only the sleep periods you confirm to Apple Health when you enable Health sync.", + "background": false } ], [ - "expo-health-connect", + "expo-notifications", { - "healthConnectPermissions": [ - "READ_SLEEP", - "WRITE_SLEEP" - ] + "icon": "./assets/icon.png", + "color": "#2196F3" } - ] + ], + "@bacons/apple-targets" ], "extra": { "eas": { diff --git a/docs/privacy/index.html b/docs/privacy/index.html new file mode 100644 index 0000000..238bb3e --- /dev/null +++ b/docs/privacy/index.html @@ -0,0 +1,84 @@ + + + + + + + + Sleep Detector Privacy Policy + + + +
+
+

Sleep Detector

+

Privacy Policy

+

Effective date: August 14, 2026

+

Sleep Detector ("the app") is provided by SimpliXio Pte. Ltd. This policy explains how the app handles information.

+ +

Summary

+

Sleep Detector is private by design. It does not require an account and does not include advertising, analytics, cross-app tracking, or a developer-operated cloud service for sleep and wellness data. Sleep records and settings are stored on the device unless the user explicitly exports them or enables Apple Health sync.

+ +

Information handled by the app

+

The app can store the following information locally:

+
    +
  • app activity timestamps used to estimate an inactivity interval;
  • +
  • sleep estimates, confirmed sleep periods, confidence details, and edits;
  • +
  • optional wellness check-ins, settings, and notification preferences; and
  • +
  • optional Apple Health information described below.
  • +
+

This information is used only to provide the app's sleep-review, history, export, and wellness features.

+ +

Apple Health and Apple Watch

+

Apple Health access is optional and requested only after the user enables Health integration. With permission, the app can read sleep stages and selected recovery signals, including heart rate variability, resting heart rate, respiratory rate, and wrist temperature, to provide private sleep context. When Health sync is enabled, the app writes only sleep periods the user has confirmed.

+

Health information is not used for advertising, sold, shared with data brokers, or transmitted to a developer-operated server. Apple Health permissions can be changed at any time in the Health or Settings app.

+

The Apple Watch companion reads permitted Apple Health information only when the user requests a refresh. It does not run a continuous workout or always-on sensor session.

+ +

Notifications

+

If the user grants notification permission, the app can schedule local bedtime reminders and confirmed-sleep summaries. Notification preferences can be changed in the app or in iOS Settings.

+ +

Export and sharing

+

JSON and CSV exports are created only when the user requests them. The user chooses where an exported file is shared. The selected destination's privacy policy applies after the file leaves the app.

+ +

Data retention and deletion

+

Sleep Detector keeps app data on the device until the user clears it in the app or uninstalls the app. Information previously written to Apple Health is controlled through the Health app and is not removed automatically when Sleep Detector is uninstalled.

+ +

Security

+

The app relies on iOS device protections and HealthKit authorization controls. No method of storage is completely risk-free, so users should keep their device and Apple ID secure.

+ +

Children

+

Sleep Detector is a general wellness tool and is not directed to children under 13. The app does not knowingly collect children's personal information.

+ +

Health disclaimer

+

Sleep Detector supports personal awareness and journaling. It is not a medical device and does not diagnose, treat, cure, or prevent any disease or sleep disorder. Users should consult a qualified healthcare professional for medical concerns.

+ +

Changes

+

This policy may be updated when the app or legal requirements change. The effective date above identifies the latest revision.

+ +

Contact

+

For privacy questions, contact contact@simplixio.tech.

+

Product creator: Pierre-Henry Soria.

+ +
© 2024–2026 Pierre-Henry Soria. Published by SimpliXio Pte. Ltd.
+
+
+ + diff --git a/eas.json b/eas.json index 5824164..f26accf 100644 --- a/eas.json +++ b/eas.json @@ -1,7 +1,7 @@ { "cli": { "version": ">= 7.3.0", - "requireCommit": false, + "requireCommit": true, "appVersionSource": "remote" }, "build": { @@ -31,10 +31,14 @@ }, "production": { "autoIncrement": true, + "env": { + "FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT": "60", + "FASTLANE_XCODEBUILD_SETTINGS_RETRIES": "4", + "GYM_XCARGS": "CODE_SIGN_IDENTITY=DF910648A6E2D59C87DBCDE44B8ACFB4E6D170D5" + }, "ios": { "resourceClass": "medium", - "credentialsSource": "remote", - "image": "macos-sequoia-15.3-xcode-16.2" + "credentialsSource": "remote" }, "android": { "buildType": "app-bundle", @@ -46,8 +50,7 @@ "submit": { "production": { "ios": { - "ascAppId": "6743064638", - "appleTeamId": "$APPLE_TEAM_ID" + "ascAppId": "6743064638" }, "android": { "serviceAccountKeyPath": "./pc-api-key.json", @@ -55,4 +58,4 @@ } } } -} \ No newline at end of file +} diff --git a/ios/Podfile b/ios/Podfile deleted file mode 100644 index e69de29..0000000 diff --git a/ios/Podfile.properties.json b/ios/Podfile.properties.json deleted file mode 100644 index e69de29..0000000 diff --git a/ios/SleepDetector/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/SleepDetector/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index e69de29..0000000 diff --git a/ios/SleepDetector/Info.plist b/ios/SleepDetector/Info.plist deleted file mode 100644 index e69de29..0000000 diff --git a/modules/sleep-intelligence/expo-module.config.json b/modules/sleep-intelligence/expo-module.config.json new file mode 100644 index 0000000..a75eacf --- /dev/null +++ b/modules/sleep-intelligence/expo-module.config.json @@ -0,0 +1,7 @@ +{ + "platforms": ["apple"], + "apple": { + "modules": ["SleepIntelligenceModule"], + "appDelegateSubscribers": ["SleepWatchAppDelegateSubscriber"] + } +} diff --git a/modules/sleep-intelligence/ios/SleepIntelligence.podspec b/modules/sleep-intelligence/ios/SleepIntelligence.podspec new file mode 100644 index 0000000..2f51c40 --- /dev/null +++ b/modules/sleep-intelligence/ios/SleepIntelligence.podspec @@ -0,0 +1,19 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'SleepIntelligence' + s.version = package['version'] + s.summary = package['description'] + s.description = package['description'] + s.license = { :type => 'MIT' } + s.author = { 'Sleep Detector' => 'support@simplixio.com' } + s.homepage = 'https://simplixio.com' + s.platforms = { :ios => '15.1' } + s.swift_version = '5.9' + s.source = { :git => 'https://example.invalid/sleep-intelligence.git' } + s.static_framework = true + s.source_files = '**/*.{h,m,mm,swift}' + s.dependency 'ExpoModulesCore' +end diff --git a/modules/sleep-intelligence/ios/SleepIntelligenceModule.swift b/modules/sleep-intelligence/ios/SleepIntelligenceModule.swift new file mode 100644 index 0000000..c1752b9 --- /dev/null +++ b/modules/sleep-intelligence/ios/SleepIntelligenceModule.swift @@ -0,0 +1,132 @@ +import ExpoModulesCore +import Foundation + +#if canImport(FoundationModels) +import FoundationModels +#endif + +private struct SleepFeatures: Decodable { + let startTime: Double + let endTime: Double + let durationMinutes: Double + let startMinuteOfDay: Int + let endMinuteOfDay: Int + let historicalSessionCount: Int + let historicalBedtimeMinute: Int? + let historicalWakeMinute: Int? + let healthKitOverlapRatio: Double? + let watchLowMotionRatio: Double? + let watchHeartRateDropPercent: Double? +} + +private struct EncodedAssessment: Encodable { + let classification: String + let confidence: Int + let suggestedStartOffsetMinutes: Int + let suggestedEndOffsetMinutes: Int + let explanation: String +} + +#if canImport(FoundationModels) +@available(iOS 26.0, *) +@Generable +private enum ModelSleepLikelihood { + case likelySleep + case uncertain + case unlikelySleep +} + +@available(iOS 26.0, *) +@Generable +private struct ModelSleepAssessment { + @Guide(description: "Whether the supplied evidence most likely represents sleep. Prefer uncertain when only app inactivity is present.") + var classification: ModelSleepLikelihood + + @Guide(description: "A conservative confidence from 0 to 100 based only on supplied evidence.") + var confidence: Int + + @Guide(description: "Suggested adjustment to the start in minutes, between -90 and 90. Use 0 without independent boundary evidence.") + var suggestedStartOffsetMinutes: Int + + @Guide(description: "Suggested adjustment to the end in minutes, between -90 and 90. Use 0 without independent boundary evidence.") + var suggestedEndOffsetMinutes: Int + + @Guide(description: "One short sentence explaining the strongest evidence and uncertainty. Do not diagnose a medical condition.") + var explanation: String +} +#endif + +public final class SleepIntelligenceModule: Module { + public func definition() -> ModuleDefinition { + Name("SleepIntelligence") + + AsyncFunction("getAvailability") { () -> [String: Any] in + #if canImport(FoundationModels) + if #available(iOS 26.0, *) { + let available = SystemLanguageModel.default.isAvailable + return [ + "available": available, + "reason": available ? "available" : "model-not-ready" + ] + } + #endif + return ["available": false, "reason": "unsupported-os"] + } + + AsyncFunction("analyzeCandidate") { (featuresJSON: String) async throws -> String in + let data = Data(featuresJSON.utf8) + let features = try JSONDecoder().decode(SleepFeatures.self, from: data) + + #if canImport(FoundationModels) + if #available(iOS 26.0, *) { + return try await Self.analyzeWithSystemModel(features, featuresJSON: featuresJSON) + } + #endif + + throw Exception(name: "ModelUnavailable", description: "Apple Foundation Models requires a supported Apple Intelligence device and operating system.") + } + + AsyncFunction("consumeWatchSnapshots") { () -> [String] in + WatchConnectivityManager.shared.consumeSnapshots() + } + + Function("getWatchState") { () -> [String: Any] in + WatchConnectivityManager.shared.state() + } + } + + #if canImport(FoundationModels) + @available(iOS 26.0, *) + private static func analyzeWithSystemModel( + _ features: SleepFeatures, + featuresJSON: String + ) async throws -> String { + guard SystemLanguageModel.default.isAvailable else { + throw Exception(name: "ModelUnavailable", description: "The on-device Apple model is not ready on this device.") + } + + let instructions = """ + You assess sleep-pattern evidence entirely on device. App inactivity alone only means the user left this app; it is weak evidence and must remain uncertain. Apple Health sleep stages and sustained Apple Watch low-motion data are independent evidence. Never infer a diagnosis, stress disorder, or anxiety disorder. Never invent measurements. Return conservative boundary adjustments and confidence. + """ + let prompt = "Assess this candidate using only the supplied validated numeric feature JSON:\n\(featuresJSON)" + let session = LanguageModelSession(model: .default, instructions: instructions) + let response = try await session.respond(to: prompt, generating: ModelSleepAssessment.self) + let content = response.content + let classification: String + switch content.classification { + case .likelySleep: classification = "likely-sleep" + case .uncertain: classification = "uncertain" + case .unlikelySleep: classification = "unlikely-sleep" + } + let encoded = EncodedAssessment( + classification: classification, + confidence: min(100, max(0, content.confidence)), + suggestedStartOffsetMinutes: min(90, max(-90, content.suggestedStartOffsetMinutes)), + suggestedEndOffsetMinutes: min(90, max(-90, content.suggestedEndOffsetMinutes)), + explanation: content.explanation + ) + let data = try JSONEncoder().encode(encoded) + return String(decoding: data, as: UTF8.self) + } + #endif +} diff --git a/modules/sleep-intelligence/ios/SleepWatchAppDelegateSubscriber.swift b/modules/sleep-intelligence/ios/SleepWatchAppDelegateSubscriber.swift new file mode 100644 index 0000000..7c72253 --- /dev/null +++ b/modules/sleep-intelligence/ios/SleepWatchAppDelegateSubscriber.swift @@ -0,0 +1,12 @@ +import ExpoModulesCore +import UIKit + +public final class SleepWatchAppDelegateSubscriber: ExpoAppDelegateSubscriber { + public func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + WatchConnectivityManager.shared.activate() + return true + } +} diff --git a/modules/sleep-intelligence/ios/WatchConnectivityManager.swift b/modules/sleep-intelligence/ios/WatchConnectivityManager.swift new file mode 100644 index 0000000..3c92a4b --- /dev/null +++ b/modules/sleep-intelligence/ios/WatchConnectivityManager.swift @@ -0,0 +1,75 @@ +import Foundation +import WatchConnectivity + +final class WatchConnectivityManager: NSObject, WCSessionDelegate, @unchecked Sendable { + static let shared = WatchConnectivityManager() + + private let appGroup = "group.com.sleepdetector.app" + private let snapshotsKey = "sleep-detector-watch-snapshot-queue-v1" + private let queue = DispatchQueue(label: "com.sleepdetector.watch-connectivity") + + private override init() { + super.init() + } + + func activate() { + guard WCSession.isSupported() else { return } + let session = WCSession.default + session.delegate = self + if session.activationState != .activated { + session.activate() + } + } + + func state() -> [String: Any] { + guard WCSession.isSupported() else { + return ["supported": false, "paired": false, "installed": false] + } + let session = WCSession.default + return [ + "supported": true, + "paired": session.isPaired, + "installed": session.isWatchAppInstalled, + ] + } + + func consumeSnapshots() -> [String] { + queue.sync { + let defaults = sharedDefaults() + let snapshots = defaults.stringArray(forKey: snapshotsKey) ?? [] + defaults.removeObject(forKey: snapshotsKey) + return snapshots + } + } + + func session( + _ session: WCSession, + activationDidCompleteWith activationState: WCSessionActivationState, + error: Error? + ) {} + + func sessionDidBecomeInactive(_ session: WCSession) {} + + func sessionDidDeactivate(_ session: WCSession) { + session.activate() + } + + func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) { + guard userInfo["kind"] as? String == "sleep-health-snapshot", + userInfo["schemaVersion"] as? Int == 1, + let payload = userInfo["payload"] as? String else { return } + + queue.async { [snapshotsKey] in + let defaults = self.sharedDefaults() + var snapshots = defaults.stringArray(forKey: snapshotsKey) ?? [] + if !snapshots.contains(payload) { + snapshots.append(payload) + } + defaults.set(Array(snapshots.suffix(30)), forKey: snapshotsKey) + } + } + + private func sharedDefaults() -> UserDefaults { + UserDefaults(suiteName: appGroup) ?? .standard + } +} diff --git a/modules/sleep-intelligence/package.json b/modules/sleep-intelligence/package.json new file mode 100644 index 0000000..d61dd09 --- /dev/null +++ b/modules/sleep-intelligence/package.json @@ -0,0 +1,12 @@ +{ + "name": "sleep-intelligence", + "version": "0.1.0", + "description": "Private on-device Apple Foundation Models bridge for sleep pattern assessment", + "main": "src/index.ts", + "private": true, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } +} diff --git a/modules/sleep-intelligence/src/index.ts b/modules/sleep-intelligence/src/index.ts new file mode 100644 index 0000000..6d9c95c --- /dev/null +++ b/modules/sleep-intelligence/src/index.ts @@ -0,0 +1,3 @@ +import { requireOptionalNativeModule } from 'expo-modules-core'; + +export default requireOptionalNativeModule('SleepIntelligence'); diff --git a/package-lock.json b/package-lock.json index 023d207..eb8d161 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,92 +1,65 @@ { "name": "detect-sleep", - "version": "1.0.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "detect-sleep", - "version": "1.0.0", + "version": "1.4.1", "dependencies": { - "@expo/vector-icons": "^14.1.0", - "@react-native-async-storage/async-storage": "^2.1.2", - "@react-native-community/datetimepicker": "8.3.0", - "@react-navigation/bottom-tabs": "^7.3.14", - "@react-navigation/native": "^7.1.10", - "@react-navigation/native-stack": "^7.3.14", - "@react-navigation/stack": "^7.3.3", + "@bacons/apple-targets": "5.0.0", + "@expo/vector-icons": "^15.0.2", + "@kingstinct/react-native-healthkit": "^14.0.2", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/datetimepicker": "8.6.0", + "@react-navigation/bottom-tabs": "^7.18.8", + "@react-navigation/native": "^7.3.8", + "@react-navigation/native-stack": "^7.17.10", + "@react-navigation/stack": "^7.10.11", "date-fns": "^4.1.0", - "expo": "53.0.12", - "expo-background-fetch": "~13.1.5", - "expo-constants": "~17.1.6", - "expo-device": "~7.1.4", - "expo-file-system": "~18.1.10", - "expo-haptics": "~14.1.4", - "expo-health-connect": "~0.1.1", - "expo-linking": "~7.1.5", - "expo-location": "~18.1.5", - "expo-notifications": "~0.31.4", - "expo-sensors": "~14.1.4", - "expo-sharing": "^13.1.5", - "expo-status-bar": "~2.2.3", - "expo-task-manager": "13.1.5", - "expo-updates": "~0.28.15", - "react": "19.0.0", - "react-native": "0.79.4", + "expo": "^55.0.0", + "expo-constants": "~55.0.17", + "expo-file-system": "~55.0.24", + "expo-font": "~55.0.8", + "expo-linking": "~55.0.16", + "expo-notifications": "~55.0.25", + "expo-sharing": "~55.0.22", + "expo-status-bar": "~55.0.6", + "expo-updates": "~55.0.26", + "react": "19.2.0", + "react-native": "0.83.10", "react-native-chart-kit": "^6.12.0", - "react-native-gesture-handler": "~2.24.0", - "react-native-health": "^1.19.0", - "react-native-reanimated": "~3.17.4", - "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.11.1", - "react-native-svg": "15.11.2" + "react-native-gesture-handler": "~2.30.0", + "react-native-nitro-modules": "^0.35.10", + "react-native-reanimated": "4.2.1", + "react-native-safe-area-context": "~5.6.2", + "react-native-screens": "~4.23.0", + "react-native-svg": "15.15.3", + "react-native-worklets": "0.7.4" }, "devDependencies": { "@babel/core": "^7.20.0", - "@types/react": "~19.0.10", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@babel/types": "^7.29.7", + "@types/jest": "^29.5.14", + "@types/react": "~19.2.10", + "@typescript-eslint/eslint-plugin": "^8.64.0", + "@typescript-eslint/parser": "^8.64.0", "eslint": "^8.56.0", - "eslint-config-universe": "^12.0.0", + "eslint-config-universe": "^16.0.0", "jest": "^29.2.1", + "jest-expo": "~55.0.20", "prettier": "^3.2.5", - "typescript": "^5.3.0" - } - }, - "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==", - "license": "MIT", - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - }, - "peerDependenciesMeta": { - "graphql": { - "optional": true - } - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "typescript": "~5.9.2" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -95,30 +68,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", - "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "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==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@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/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -134,15 +107,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", - "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.3", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -150,25 +123,25 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -178,17 +151,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "engines": { @@ -199,13 +172,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -216,56 +189,65 @@ } }, "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.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "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.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -275,35 +257,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -313,14 +295,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -330,67 +312,67 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", - "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -483,12 +465,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", - "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -498,14 +480,14 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz", - "integrity": "sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-decorators": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -515,12 +497,12 @@ } }, "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz", - "integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.29.7.tgz", + "integrity": "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -581,12 +563,12 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", - "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -608,12 +590,12 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.27.1.tgz", - "integrity": "sha512-eBC/3KSekshx19+N40MzjWqJd7KTEdOoLesAfa4IDFI8eRz5a47i5Oszus6zG/cwIXN63YhgLOMSSNJx49sENg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.29.7.tgz", + "integrity": "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -623,12 +605,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", - "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", + "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -677,12 +659,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -794,12 +776,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -824,14 +806,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", - "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -841,14 +823,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -858,12 +840,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", - "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -873,13 +855,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -888,18 +870,34 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -909,13 +907,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -925,12 +923,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.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -940,12 +939,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -955,13 +954,13 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", - "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", + "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-flow": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-flow": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -971,13 +970,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -987,14 +986,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1004,12 +1003,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1019,12 +1018,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1034,13 +1033,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1050,13 +1049,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1066,12 +1065,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1081,12 +1080,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1096,15 +1095,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.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "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/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1114,12 +1114,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1129,13 +1129,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1145,12 +1145,12 @@ } }, "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1160,13 +1160,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1176,14 +1176,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1193,12 +1193,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz", - "integrity": "sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1208,16 +1208,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1227,12 +1227,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" + "@babel/plugin-transform-react-jsx": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1242,12 +1242,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1257,12 +1257,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1272,13 +1272,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1288,12 +1288,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", - "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", + "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1303,16 +1303,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", - "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "engines": { @@ -1338,13 +1338,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", + "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1354,12 +1354,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1384,16 +1384,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", - "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1419,17 +1419,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", - "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.27.1", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1439,16 +1439,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1467,67 +1467,178 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "node_modules/@bacons/apple-targets": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@bacons/apple-targets/-/apple-targets-5.0.0.tgz", + "integrity": "sha512-03LEidnuAAccH5ueL03sOaauQMnpJ7ZGwrVUlzNc5K3BrlfGE9SDUJs1ITkXC7YLKfFPJyiB7VzoZYiRxJmUdA==", + "license": "MIT", + "dependencies": { + "@bacons/xcode": "1.0.0-alpha.32", + "@expo/prebuild-config": "~55.0.6", + "@react-native/normalize-colors": "^0.79.2", + "debug": "^4.3.4", + "glob": "^10.4.2" + }, + "peerDependencies": { + "expo": ">=52" + } + }, + "node_modules/@bacons/apple-targets/node_modules/@react-native/normalize-colors": { + "version": "0.79.7", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.7.tgz", + "integrity": "sha512-RrvewhdanEWhlyrHNWGXGZCc6MY0JGpNgRzA8y6OomDz0JmlnlIsbBHbNpPnIrt9Jh2KaV10KTscD1Ry8xU9gQ==", + "license": "MIT" + }, + "node_modules/@bacons/apple-targets/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@bacons/apple-targets/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bacons/apple-targets/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/@bacons/apple-targets/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bacons/apple-targets/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bacons/xcode": { + "version": "1.0.0-alpha.32", + "resolved": "https://registry.npmjs.org/@bacons/xcode/-/xcode-1.0.0-alpha.32.tgz", + "integrity": "sha512-OGpH7+yMbWC2cgYZon5B+VVadH9HsB2V/abtEiplA65XnSuV4GAYAVixOCDc5k182WkfoakfdM0zW6U9cbcsbw==", + "license": "MIT", + "dependencies": { + "@expo/plist": "^0.0.18", + "debug": "^4.3.4", + "uuid": "^8.3.2" + } + }, + "node_modules/@bacons/xcode/node_modules/@expo/plist": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.18.tgz", + "integrity": "sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "~0.7.0", + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0" + } + }, + "node_modules/@bacons/xcode/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@bacons/xcode/node_modules/xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "license": "MIT", + "engines": { + "node": ">=8.0" } }, "node_modules/@bcoe/v8-coverage": { @@ -1550,9 +1661,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1569,9 +1680,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -1602,17 +1713,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -1630,9 +1730,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1665,165 +1765,58 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@expo/cli": { - "version": "0.24.15", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.15.tgz", - "integrity": "sha512-RDZS30OSnbXkSPnBXdyPL29KbltjOmegE23bZZDiGV23WOReWcPgRc5U7Fd8eLPhtRjHBKlBpNJMTed5Ntr/uw==", - "license": "MIT", - "dependencies": { - "@0no-co/graphql.web": "^1.0.8", - "@babel/runtime": "^7.20.0", - "@expo/code-signing-certificates": "^0.0.5", - "@expo/config": "~11.0.10", - "@expo/config-plugins": "~10.0.3", - "@expo/devcert": "^1.1.2", - "@expo/env": "~1.0.5", - "@expo/image-utils": "^0.7.4", - "@expo/json-file": "^9.1.4", - "@expo/metro-config": "~0.20.15", - "@expo/osascript": "^2.2.4", - "@expo/package-manager": "^1.8.4", - "@expo/plist": "^0.3.4", - "@expo/prebuild-config": "^9.0.7", - "@expo/spawn-async": "^1.7.2", - "@expo/ws-tunnel": "^1.0.1", - "@expo/xcpretty": "^4.3.0", - "@react-native/dev-middleware": "0.79.4", - "@urql/core": "^5.0.6", - "@urql/exchange-retry": "^1.3.0", - "accepts": "^1.3.8", - "arg": "^5.0.2", - "better-opn": "~3.0.2", - "bplist-creator": "0.1.0", - "bplist-parser": "^0.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.3.0", - "compression": "^1.7.4", - "connect": "^3.7.0", - "debug": "^4.3.4", - "env-editor": "^0.4.1", - "freeport-async": "^2.0.0", - "getenv": "^2.0.0", - "glob": "^10.4.2", - "lan-network": "^0.1.6", - "minimatch": "^9.0.0", - "node-forge": "^1.3.1", - "npm-package-arg": "^11.0.0", - "ora": "^3.4.0", - "picomatch": "^3.0.1", - "pretty-bytes": "^5.6.0", - "pretty-format": "^29.7.0", - "progress": "^2.0.3", - "prompts": "^2.3.2", - "qrcode-terminal": "0.11.0", - "require-from-string": "^2.0.2", - "requireg": "^0.2.2", - "resolve": "^1.22.2", - "resolve-from": "^5.0.0", - "resolve.exports": "^2.0.3", - "semver": "^7.6.0", - "send": "^0.19.0", - "slugify": "^1.3.4", - "source-map-support": "~0.5.21", - "stacktrace-parser": "^0.1.10", - "structured-headers": "^0.4.1", - "tar": "^7.4.3", - "terminal-link": "^2.1.1", - "undici": "^6.18.2", - "wrap-ansi": "^7.0.0", - "ws": "^8.12.1" - }, - "bin": { - "expo-internal": "build/bin/cli" - } - }, - "node_modules/@expo/cli/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@expo/cli/node_modules/semver": { - "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" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@expo/code-signing-certificates": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz", - "integrity": "sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", + "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", "license": "MIT", "dependencies": { - "node-forge": "^1.2.1", - "nullthrows": "^1.1.1" + "node-forge": "^1.3.3" } }, "node_modules/@expo/config": { - "version": "11.0.13", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-11.0.13.tgz", - "integrity": "sha512-TnGb4u/zUZetpav9sx/3fWK71oCPaOjZHoVED9NaEncktAd0Eonhq5NUghiJmkUGt3gGSjRAEBXiBbbY9/B1LA==", + "version": "55.0.19", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.19.tgz", + "integrity": "sha512-MahrCR6LsElK/1r/C/zfEZ5Pdgmo88Gtd4CCnASv/rC2pUIB+ENt2oKRHAPIWi7McTeoqDPb9KwCkQBpNMOjrg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~10.1.2", - "@expo/config-types": "^53.0.5", - "@expo/json-file": "^9.1.5", + "@expo/config-plugins": "~55.0.11", + "@expo/config-types": "^55.0.6", + "@expo/json-file": "^10.0.15", + "@expo/require-utils": "^55.0.6", "deepmerge": "^4.3.1", "getenv": "^2.0.0", - "glob": "^10.4.2", - "require-from-string": "^2.0.2", - "resolve-from": "^5.0.0", + "glob": "^13.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", - "slugify": "^1.3.4", - "sucrase": "3.35.0" + "slugify": "^1.3.4" } }, "node_modules/@expo/config-plugins": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.0.3.tgz", - "integrity": "sha512-fjCckkde67pSDf48x7wRuPsgQVIqlDwN7NlOk9/DFgQ1hCH0L5pGqoSmikA1vtAyiA83MOTpkGl3F3wyATyUog==", + "version": "55.0.11", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.11.tgz", + "integrity": "sha512-85ZSmIK8rMfvYbG/2IHtwnykVdb6LI0ZavduM3ZwUMThyyVegYjVsO5Zek2ec8xzPhCmYX0ar5onnFEcwUDUlw==", "license": "MIT", "dependencies": { - "@expo/config-types": "^53.0.4", - "@expo/json-file": "~9.1.4", - "@expo/plist": "^0.3.4", + "@expo/config-types": "^55.0.6", + "@expo/json-file": "~10.0.15", + "@expo/plist": "^0.5.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", - "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, - "node_modules/@expo/config-plugins/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@expo/config-plugins/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1833,55 +1826,15 @@ } }, "node_modules/@expo/config-types": { - "version": "53.0.5", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.5.tgz", - "integrity": "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.6.tgz", + "integrity": "sha512-S+GJKYoIjnWlert/9vXuTohaTsMbyOLSVxdIgPgoq3P4N1p4CWrfyZLnz6qRug8wYSO5fcYcS9mFleyEP8wRLg==", "license": "MIT" }, - "node_modules/@expo/config/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/config/node_modules/@expo/config-plugins": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.2.tgz", - "integrity": "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==", - "license": "MIT", - "dependencies": { - "@expo/config-types": "^53.0.5", - "@expo/json-file": "~9.1.5", - "@expo/plist": "^0.3.5", - "@expo/sdk-runtime-versions": "^1.0.0", - "chalk": "^4.1.2", - "debug": "^4.3.5", - "getenv": "^2.0.0", - "glob": "^10.4.2", - "resolve-from": "^5.0.0", - "semver": "^7.5.4", - "slash": "^3.0.0", - "slugify": "^1.6.6", - "xcode": "^3.0.1", - "xml2js": "0.6.0" - } - }, - "node_modules/@expo/config/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@expo/config/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1891,14 +1844,13 @@ } }, "node_modules/@expo/devcert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.0.tgz", - "integrity": "sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz", + "integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==", "license": "MIT", "dependencies": { "@expo/sudo-prompt": "^9.3.1", - "debug": "^3.1.0", - "glob": "^10.4.2" + "debug": "^3.1.0" } }, "node_modules/@expo/devcert/node_modules/debug": { @@ -1910,44 +1862,56 @@ "ms": "^2.1.1" } }, + "node_modules/@expo/devtools": { + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-55.0.3.tgz", + "integrity": "sha512-KoIDgo0NoXeWLsIcOdZqtAG/1LlsM+JL0DA3bo0vCYaOYTBLXi/ZvRBqa20Ub8D2vKLNa+FgRQW0gRg04Ps1Pg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@expo/env": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-1.0.7.tgz", - "integrity": "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.1.3.tgz", + "integrity": "sha512-Rhu/lJ1kOhqzJvLwW0iB4WKUzB1nFa8WBwXFL44qkSTNwibjrbI8lA46Ix6W2MMTdlUqL1m85Gdyx0T7nGpREg==", "license": "MIT", "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" - } - }, - "node_modules/@expo/env/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", - "license": "MIT", + }, "engines": { - "node": ">=6" + "node": ">=20.12.0" } }, "node_modules/@expo/fingerprint": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.13.1.tgz", - "integrity": "sha512-MgZ5uIvvwAnjWeQoj4D3RnBXjD1GNOpCvhp2jtZWdQ8yEokhDEJGoHjsMT8/NCB5m2fqP5sv2V5nPzC7CN1YjQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.8.tgz", + "integrity": "sha512-RaLOikl+alkvG9ZrBQFGi3kVXJdG5GQXY1H3V1ZFCwFyhFikwuBozVrl4KL7U+N0Tm8Bf1qDmpTEIx8JOyrQog==", "license": "MIT", "dependencies": { + "@expo/env": "^2.1.3", "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", - "find-up": "^5.0.0", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", "ignore": "^5.3.1", - "minimatch": "^9.0.0", - "p-limit": "^3.1.0", + "minimatch": "^10.2.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, @@ -1955,19 +1919,24 @@ "fingerprint": "bin/cli.js" } }, - "node_modules/@expo/fingerprint/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", + "node_modules/@expo/fingerprint/node_modules/@expo/env": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.4.2.tgz", + "integrity": "sha512-28pqaEqwnmLduZ00Pq9HkSzE5wbj1MTwp5/n8nm8rD8MCjR9eUnVOwmNksPI3Be2ReAPO/DbPn1puy0mvoocsQ==", "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "debug": "^4.3.4", + "getenv": "^2.0.0" + }, "engines": { - "node": ">=6" + "node": ">=20.12.0" } }, "node_modules/@expo/fingerprint/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1977,35 +1946,24 @@ } }, "node_modules/@expo/image-utils": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.7.6.tgz", - "integrity": "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw==", + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.15.tgz", + "integrity": "sha512-3f5CgKJnJ8m4mp8VTcAFQqLrxof99RDr77Aa+7hgzDPg3ZotjLnofl77hf+COQRYi/kuqRYdYFgPIqOIOIdWJA==", "license": "MIT", "dependencies": { + "@expo/require-utils": "^55.0.6", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", - "resolve-from": "^5.0.0", - "semver": "^7.6.0", - "temp-dir": "~2.0.0", - "unique-string": "~2.0.0" - } - }, - "node_modules/@expo/image-utils/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", - "license": "MIT", - "engines": { - "node": ">=6" + "semver": "^7.6.0" } }, "node_modules/@expo/image-utils/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2015,9 +1973,9 @@ } }, "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==", + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.16.tgz", + "integrity": "sha512-fcVkWEj+hLuP2yt5W0aw6LmDRqSPWDLUSxOMcmFeV+algmIF59sQVKCwB9btjQLd4V6x9N0pISkQEkBubUHrCw==", "license": "MIT", "dependencies": { "@babel/code-frame": "~7.10.4", @@ -2033,102 +1991,160 @@ "@babel/highlight": "^7.10.4" } }, + "node_modules/@expo/local-build-cache-provider": { + "version": "55.0.15", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.15.tgz", + "integrity": "sha512-coFNPt2gGmWtTJwqHlvx/Us9/VWK+pTHuP4jv/NM5jaHZuN0FhX7m2FvwthbluHUGpaVBc8+AxEDmDVqTfKtHg==", + "license": "MIT", + "dependencies": { + "@expo/config": "~55.0.19", + "chalk": "^4.1.2" + } + }, + "node_modules/@expo/metro": { + "version": "55.1.1", + "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-55.1.1.tgz", + "integrity": "sha512-/wfXo5hTuAVpVLG/4hzlmD9NBGJkzkmBEMm/4VICajYRbj7y8OmqqPWbbymzHiBiHB6tI9BnsyXpQM6zVZEECg==", + "license": "MIT", + "dependencies": { + "metro": "0.83.7", + "metro-babel-transformer": "0.83.7", + "metro-cache": "0.83.7", + "metro-cache-key": "0.83.7", + "metro-config": "0.83.7", + "metro-core": "0.83.7", + "metro-file-map": "0.83.7", + "metro-minify-terser": "0.83.7", + "metro-resolver": "0.83.7", + "metro-runtime": "0.83.7", + "metro-source-map": "0.83.7", + "metro-symbolicate": "0.83.7", + "metro-transform-plugins": "0.83.7", + "metro-transform-worker": "0.83.7" + } + }, "node_modules/@expo/metro-config": { - "version": "0.20.15", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.20.15.tgz", - "integrity": "sha512-m8i58IQ7I8iOdVRfOhFmhPMHuhgeTVfQp1+mxW7URqPZaeVbuDVktPqOiNoHraKBoGPLKMUSsD+qdUuJVL3wMg==", + "version": "55.0.25", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.25.tgz", + "integrity": "sha512-3KXVaIdawin16YXXDK4P3HGYjnlNqq/34dqb0kvoTM9RBDWbuq+zYkanLRHKLrDkDLODv7vSvbQNAATt76my0Q==", "license": "MIT", "dependencies": { + "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "@expo/config": "~11.0.10", - "@expo/env": "~1.0.5", - "@expo/json-file": "~9.1.4", + "@expo/config": "~55.0.19", + "@expo/env": "~2.1.3", + "@expo/json-file": "~10.0.15", + "@expo/metro": "~55.1.1", "@expo/spawn-async": "^1.7.2", + "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", + "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", - "lightningcss": "~1.27.0", - "minimatch": "^9.0.0", - "postcss": "~8.4.32", + "lightningcss": "^1.30.1", + "picomatch": "^4.0.3", + "postcss": "^8.5.14", "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "expo": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } } }, - "node_modules/@expo/metro-config/node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", + "node_modules/@expo/metro-config/node_modules/hermes-estree": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", + "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", + "license": "MIT" + }, + "node_modules/@expo/metro-config/node_modules/hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", + "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "hermes-estree": "0.32.1" } }, "node_modules/@expo/osascript": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.2.4.tgz", - "integrity": "sha512-Q+Oyj+1pdRiHHpev9YjqfMZzByFH8UhKvSszxa0acTveijjDhQgWrq4e9T/cchBHi0GWZpGczWyiyJkk1wM1dg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.7.1.tgz", + "integrity": "sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==", "license": "MIT", "dependencies": { - "@expo/spawn-async": "^1.7.2", - "exec-async": "^2.2.0" + "@expo/spawn-async": "^1.8.0" }, "engines": { "node": ">=12" } }, "node_modules/@expo/package-manager": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.8.4.tgz", - "integrity": "sha512-8H8tLga/NS3iS7QaX/NneRPqbObnHvVCfMCo0ShudreOFmvmgqhYjRlkZTRstSyFqefai8ONaT4VmnLHneRYYg==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.13.1.tgz", + "integrity": "sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==", "license": "MIT", "dependencies": { - "@expo/json-file": "^9.1.4", - "@expo/spawn-async": "^1.7.2", + "@expo/json-file": "^11.0.1", + "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, + "node_modules/@expo/package-manager/node_modules/@expo/json-file": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-11.0.1.tgz", + "integrity": "sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "json5": "^2.2.3" + } + }, "node_modules/@expo/plist": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.3.5.tgz", - "integrity": "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.4.tgz", + "integrity": "sha512-Jqppj0FULNq6Zp5JtQrFICl8TtpMjwwUbxEcEC2T3z7m+TOrTQEHZXz3D3Ay7vhbmvD+VMgfWJ4ARclJXeN8Eg==", "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", - "base64-js": "^1.2.3", + "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "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": "55.0.20", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.20.tgz", + "integrity": "sha512-Cy9xnwK0f3aG0TbKkBFuVQCtd6rj22muYfj7o1iCAE0NOUy5ks1k2vcmlKD1g48wzcWw0hFySiwtaGsB9qeVRQ==", "license": "MIT", "dependencies": { - "@expo/config": "~11.0.10", - "@expo/config-plugins": "~10.0.3", - "@expo/config-types": "^53.0.4", - "@expo/image-utils": "^0.7.4", - "@expo/json-file": "^9.1.4", - "@react-native/normalize-colors": "0.79.4", + "@expo/config": "~55.0.19", + "@expo/config-plugins": "~55.0.11", + "@expo/config-types": "^55.0.6", + "@expo/image-utils": "^0.8.15", + "@expo/json-file": "^10.0.15", + "@react-native/normalize-colors": "0.83.10", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" + }, + "peerDependencies": { + "expo": "*" } }, "node_modules/@expo/prebuild-config/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2137,6 +2153,31 @@ "node": ">=10" } }, + "node_modules/@expo/require-utils": { + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.6.tgz", + "integrity": "sha512-IqsRGPdGbF0lAIrg3XQ+GzcITYcsskvIRmFAjw2kBnr8RgSrRhFAJY1bKksukEUsuZy0knhTCVIIUce7hocqeA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8" + }, + "peerDependencies": { + "typescript": "^5.0.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@expo/schema-utils": { + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.5.tgz", + "integrity": "sha512-wdV4SzWJ/l+6y/r9vDaqPqXGyfxUD1igbX8rRbRBfkVXenAUY2qenq6HF4S0iJ0pjpVDNTANn5aQY7VV55hhsA==", + "license": "MIT" + }, "node_modules/@expo/sdk-runtime-versions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", @@ -2144,12 +2185,12 @@ "license": "MIT" }, "node_modules/@expo/spawn-async": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz", - "integrity": "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.8.0.tgz", + "integrity": "sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3" + "cross-spawn": "^7.0.6" }, "engines": { "node": ">=12" @@ -2162,12 +2203,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": "15.1.1", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz", + "integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==", "license": "MIT", "peerDependencies": { - "expo-font": "*", + "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } @@ -2179,29 +2220,19 @@ "license": "MIT" }, "node_modules/@expo/xcpretty": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.3.2.tgz", - "integrity": "sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.4.tgz", + "integrity": "sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==", "license": "BSD-3-Clause", "dependencies": { - "@babel/code-frame": "7.10.4", + "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", - "find-up": "^5.0.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, - "node_modules/@expo/xcpretty/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/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2218,21 +2249,10 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2264,12 +2284,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@ide/backoff": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz", - "integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==", - "license": "MIT" - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2288,9 +2302,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -2300,9 +2314,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -2312,12 +2326,12 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -2343,22 +2357,10 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "license": "ISC", "engines": { "node": ">=12" @@ -2403,9 +2405,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -2660,17 +2662,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2694,9 +2685,9 @@ } }, "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2809,17 +2800,23 @@ } }, "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.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "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" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -2831,19 +2828,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.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2857,15 +2845,30 @@ "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.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kingstinct/react-native-healthkit": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@kingstinct/react-native-healthkit/-/react-native-healthkit-14.0.2.tgz", + "integrity": "sha512-ed6x7xtSaML8qy8qswJWlVPKImeNEvHfWtuzylQZ9MsxgLriv33gDjf/8iWIcB4SKCW/BZNOFBcCqTQq5i5zmw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kingstinct" + }, + "peerDependencies": { + "react": ">=19", + "react-native": ">=0.79", + "react-native-nitro-modules": ">=0.35" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2928,9 +2931,9 @@ } }, "node_modules/@react-native-async-storage/async-storage": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", - "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", "license": "MIT", "dependencies": { "merge-options": "^3.0.4" @@ -2940,15 +2943,15 @@ } }, "node_modules/@react-native-community/datetimepicker": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.3.0.tgz", - "integrity": "sha512-K/KgaJbLtjMpx4PaG4efrVIcSe6+DbLufeX1lwPB5YY8i3sq9dOh6WcAcMTLbaRTUpurebQTkl7puHPFm9GalA==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.6.0.tgz", + "integrity": "sha512-yxPSqNfxgpGaqHQIpatqe6ykeBdU/1pdsk/G3x01mY2bpTflLpmVTLqFSJYd3MiZzxNZcMs/j1dQakUczSjcYA==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { - "expo": ">=50.0.0", + "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" @@ -2963,31 +2966,31 @@ } }, "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", - "integrity": "sha512-7PjHNRtYlc36B7P1PHme8ZV0ZJ/xsA/LvMoXe6EX++t7tSPJ8iYCMBryZhcdnztgce73b94Hfx6TTGbLF+xtUg==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.10.tgz", + "integrity": "sha512-AcVfyQ+8HsWCecXEnSaRffP71KqaWrhNB8bLulYCpKO7i5h/lmwgF191uafU/WiS0LbEMTGlXwWBshPto1phyA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.79.4.tgz", - "integrity": "sha512-quhytIlDedR3ircRwifa22CaWVUVnkxccrrgztroCZaemSJM+HLurKJrjKWm0J5jV9ed+d+9Qyb1YB0syTHDjg==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.10.tgz", + "integrity": "sha512-iRfhxsOePloy12TUzpL9dCwnxLi3eurg8+JurSozAmWh4HtmKu0IJHNjbEKk9htI5n4RlFom8LQ9vVcvgWOTbw==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.79.4" + "@react-native/codegen": "0.83.10" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-preset": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.79.4.tgz", - "integrity": "sha512-El9JvYKiNfnkQ3qR7zJvvRdP3DX2i4BGYlIricWQishI3gWAfm88FQYFC2CcGoMQWJQEPN4jnDMpoISAJDEN4g==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.10.tgz", + "integrity": "sha512-wTZWvs5cUQqr2qBAoJNKUy6IBH7wqPAJdxTC834S/7vBqtXypYHxDAFzYXkIh8pRTC86IBYxyc2IrBYHnB8O2g==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -3031,52 +3034,44 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.79.4", - "babel-plugin-syntax-hermes-parser": "0.25.1", + "@react-native/babel-plugin-codegen": "0.83.10", + "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { "@babel/core": "*" } }, "node_modules/@react-native/codegen": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.79.4.tgz", - "integrity": "sha512-K0moZDTJtqZqSs+u9tnDPSxNsdxi5irq8Nu4mzzOYlJTVNGy5H9BiIDg/NeKGfjAdo43yTDoaPSbUCvVV8cgIw==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.10.tgz", + "integrity": "sha512-rTcuuwRDPLrBmhXc9vAr2gispQFCEqd2WVPh2+N5eV80p8tf9mHy7CoFnmPy8A8csK6HGlANaD9SMPvccQjh4g==", "license": "MIT", "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", "glob": "^7.1.1", - "hermes-parser": "0.25.1", + "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { "@babel/core": "*" } }, - "node_modules/@react-native/codegen/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@react-native/codegen/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -3093,10 +3088,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@react-native/codegen/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "license": "MIT" + }, + "node_modules/@react-native/codegen/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, "node_modules/@react-native/codegen/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3106,51 +3116,39 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.79.4.tgz", - "integrity": "sha512-lx1RXEJwU9Tcs2B2uiDZBa6yghU6m6STvwYqHbJlFZyNN1k3JRa9j0/CDu+0fCFacIn7rEfZpb4UWi5YhsHpQg==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.10.tgz", + "integrity": "sha512-7QmZ7FLAIJbYjgxILBUE1k71lQB6atXZSLRczKE6Iti+BO3XHNiDSCZxuxUkYUJI0V/0kCbO/M8/a5zqHNu+Rw==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.79.4", - "chalk": "^4.0.0", - "debug": "^2.2.0", + "@react-native/dev-middleware": "0.83.10", + "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.82.0", - "metro-config": "^0.82.0", - "metro-core": "^0.82.0", + "metro": "^0.83.6", + "metro-config": "^0.83.6", + "metro-core": "^0.83.6", "semver": "^7.1.3" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@react-native-community/cli": "*" + "@react-native-community/cli": "*", + "@react-native/metro-config": "*" }, "peerDependenciesMeta": { "@react-native-community/cli": { "optional": true + }, + "@react-native/metro-config": { + "optional": true } } }, - "node_modules/@react-native/community-cli-plugin/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/@react-native/community-cli-plugin/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3160,101 +3158,107 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.4.tgz", - "integrity": "sha512-Gg4LhxHIK86Bi2RiT1rbFAB6fuwANRsaZJ1sFZ1OZEMQEx6stEnzaIrmfgzcv4z0bTQdQ8lzCrpsz0qtdaD4eA==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.10.tgz", + "integrity": "sha512-AlSOMdXoaSXi4O82f3APvw14e+EfrEvwPerfH2AI3JUrD/yQjFtbIxeyRPd89/MlKIbZU4cwyVFqj96bj39J5g==", "license": "BSD-3-Clause", "engines": { - "node": ">=18" + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.10.tgz", + "integrity": "sha512-hk9xFI9H412XSX1lpVuKrnxUQe3zIPKxFceYPUwx2L5aZQQ/yjypWkLs+LLldV6eh93B2sBnZbns1oFSE3LQNw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": ">= 20.19.4" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.79.4.tgz", - "integrity": "sha512-OWRDNkgrFEo+OSC5QKfiiBmGXKoU8gmIABK8rj2PkgwisFQ/22p7MzE5b6oB2lxWaeJT7jBX5KVniNqO46VhHA==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.10.tgz", + "integrity": "sha512-V39TcESd9LGzDBs7PYVsx5oE1oGv1dLC0GIyJyEyehZjmOYk5sJ4C0m1ATKPeDE7JhiY8s/p6pNOBNp+NF4FGQ==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.79.4", + "@react-native/debugger-frontend": "0.83.10", + "@react-native/debugger-shell": "0.83.10", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", - "debug": "^2.2.0", + "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", - "ws": "^6.2.3" + "ws": "^7.5.10" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">= 20.19.4" } }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.12.tgz", + "integrity": "sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg==", "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.79.4.tgz", - "integrity": "sha512-Gv5ryy23k7Sib2xVgqw65GTryg9YTij6URcMul5cI7LRcW0Aa1/FPb26l388P4oeNGNdDoAkkS+CuCWNunRuWg==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.10.tgz", + "integrity": "sha512-DpWzrcmxAEgD/tvy4r4WH10PYJPdfDCvtctVZ2Ez0sHlNYgWI6STfqxeAcZhU84hMhNsTec3IHDxNJ2cG8xPjQ==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.79.4.tgz", - "integrity": "sha512-VyKPo/l9zP4+oXpQHrJq4vNOtxF7F5IMdQmceNzTnRpybRvGGgO/9jYu9mdmdKRO2KpQEc5dB4W2rYhVKdGNKg==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.10.tgz", + "integrity": "sha512-q28LKHga5lf2ZX4u1T8Bj5RXqyzOBRWSXVjCCdczj/oacAVZPUrandGC1E9fR35fujyb3ZCqZbIQCvs8MhsTNA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, - "node_modules/@react-native/normalize-color": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", - "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==", - "license": "MIT" - }, "node_modules/@react-native/normalize-colors": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.4.tgz", - "integrity": "sha512-247/8pHghbYY2wKjJpUsY6ZNbWcdUa5j5517LZMn6pXrbSSgWuj3JA4OYibNnocCHBaVrt+3R8XC3VEJqLlHFg==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.10.tgz", + "integrity": "sha512-dWgqcxaBy27oLx9tndcTF0917vbLOOstyNjYD58J6Z7YxkEZSaKG6+A+3I1KV6O1Xg2D69QThp4t6ezoC3NiGA==", "license": "MIT" }, "node_modules/@react-navigation/bottom-tabs": { - "version": "7.3.14", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.3.14.tgz", - "integrity": "sha512-s2qinJggS2HYZdCOey9A+fN+bNpWeEKwiL/FjAVOTcv+uofxPWN6CtEZUZGPEjfRjis/srURBmCmpNZSI6sQ9Q==", + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.8.tgz", + "integrity": "sha512-7KCsBtwCRwQQSTEw9SLglZCEkxWc+EqgdRKyUOgII+6+xEnemOyg8aDlnLIM2yk9ip6uE+Oxvm0jdpQU2vsu2w==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.4.3", - "color": "^4.2.3" + "@react-navigation/elements": "^2.9.30", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { - "@react-navigation/native": "^7.1.10", + "@react-navigation/native": "^7.3.8", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", @@ -3262,17 +3266,18 @@ } }, "node_modules/@react-navigation/core": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.10.0.tgz", - "integrity": "sha512-qZBA5gGm+9liT4+EHk+kl9apwvqh7HqhLF1XeX6SQRmC/n2QI0u1B8OevKc+EPUDEM9Od15IuwT/GRbSs7/Umw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.21.5.tgz", + "integrity": "sha512-3hpV7uR41LBW+GHDoLhztZCb/i5ySRJISZ/rez4d7DCHSZo6ej4gNxYclaS6LRguoLiKG7SOCNa6O390AQklZQ==", "license": "MIT", "dependencies": { - "@react-navigation/routers": "^7.4.0", + "@react-navigation/routers": "^7.6.0", "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", - "use-latest-callback": "^0.2.3", + "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { @@ -3280,16 +3285,18 @@ } }, "node_modules/@react-navigation/elements": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.4.3.tgz", - "integrity": "sha512-psoNmnZ0DQIt9nxxPITVLtYW04PGCAfnmd/Pcd3yhiBs93aj+HYKH+SDZDpUnXMf3BN7Wvo4+jPI+/Xjqb+m9w==", + "version": "2.9.30", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.30.tgz", + "integrity": "sha512-2isleieiRMmP4WNMV2Q1u3qP1M47ZqsJ2hJ/Og11FeKXK8YmUTHya7PW7ecsgAh2CXKTxAcbfJDFTSvq2D++Tw==", "license": "MIT", "dependencies": { - "color": "^4.2.3" + "color": "^4.2.3", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.1.10", + "@react-navigation/native": "^7.3.8", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" @@ -3301,16 +3308,17 @@ } }, "node_modules/@react-navigation/native": { - "version": "7.1.10", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.10.tgz", - "integrity": "sha512-Ug4IML0DkAxZTMF/E7lyyLXSclkGAYElY2cxZWITwfBjtlVeda0NjsdnTWY5EGjnd7bwvhTIUC+CO6qSlrDn5A==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.8.tgz", + "integrity": "sha512-zHmQcxWBT8GOwsofEOmHqpdM5twkwE/esa9JFGlW4hpXeQTTe/dRcPSLjwsvePtolbDKz0YZbK+I5KrW3j63LQ==", "license": "MIT", "dependencies": { - "@react-navigation/core": "^7.10.0", + "@react-navigation/core": "^7.21.5", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", - "use-latest-callback": "^0.2.3" + "standard-navigation": "^0.0.7", + "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", @@ -3318,16 +3326,18 @@ } }, "node_modules/@react-navigation/native-stack": { - "version": "7.3.14", - "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.3.14.tgz", - "integrity": "sha512-45Sf7ReqSCIySXS5nrKtLGmNlFXm5x+u32YQMwKDONCqVGOBCfo4ryKqeQq1EMJ7Py6IDyOwHMhA+jhNOxnfPw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.17.10.tgz", + "integrity": "sha512-m1BWVEaOPX9k30DbmhsD7IlrUdl4J7Ogmo71rcNlbZQPMNB126av/nfwqB+qN7DIsiwTAnORnT+SwzD56qqD0Q==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.4.3", + "@react-navigation/elements": "^2.9.30", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { - "@react-navigation/native": "^7.1.10", + "@react-navigation/native": "^7.3.8", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", @@ -3335,25 +3345,26 @@ } }, "node_modules/@react-navigation/routers": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.4.0.tgz", - "integrity": "sha512-th5THnuWKJlmr7GGHiicy979di11ycDWub9iIXbEDvQwmwmsRzppmVbfs2nD8bC/MgyMgqWu/gxfys+HqN+kcw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.6.0.tgz", + "integrity": "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==", "license": "MIT", "dependencies": { "nanoid": "^3.3.11" } }, "node_modules/@react-navigation/stack": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-7.3.3.tgz", - "integrity": "sha512-TIUiQKSFYjVwRAT5pFn1jIk4aqPMgeh5eTXzsxgnAxrqrmE9E1o1XaZj+aeqBhTeBKxPTaRSjzsayD2/cue0mA==", + "version": "7.10.11", + "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-7.10.11.tgz", + "integrity": "sha512-FNlylwYaGwGArRjmJhmL3FviTjT/j10UVx6TAaqqeLeYEircqMm2kBBz2e9FpcXH2OeCnPinAjZ0B1th2mNmsg==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.4.3", - "color": "^4.2.3" + "@react-navigation/elements": "^2.9.30", + "color": "^4.2.3", + "use-latest-callback": "^0.2.4" }, "peerDependencies": { - "@react-navigation/native": "^7.1.10", + "@react-navigation/native": "^7.3.8", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", @@ -3392,6 +3403,16 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3472,12 +3493,28 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } }, "node_modules/@types/json5": { "version": "0.0.29", @@ -3496,28 +3533,28 @@ } }, "node_modules/@types/react": { - "version": "19.0.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.14.tgz", - "integrity": "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -3534,137 +3571,159 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -3672,38 +3731,37 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3714,101 +3772,83 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, "license": "ISC" }, - "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==", - "license": "MIT", - "dependencies": { - "@0no-co/graphql.web": "^1.0.5", - "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==", - "license": "MIT", - "dependencies": { - "@urql/core": "^5.1.1", - "wonka": "^6.3.2" - }, - "peerDependencies": { - "@urql/core": "^5.0.0" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", "license": "MIT", "engines": { "node": ">=10.0.0" } }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3835,9 +3875,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3846,6 +3886,17 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -3856,19 +3907,32 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3927,12 +3991,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -3947,9 +4005,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -4008,16 +4066,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -4144,19 +4192,6 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -4167,16 +4202,18 @@ "node": ">= 0.4" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -4257,13 +4294,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.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -4271,43 +4308,67 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "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.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, "node_modules/babel-plugin-react-native-web": { - "version": "0.19.13", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.13.tgz", - "integrity": "sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", + "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", "license": "MIT" }, "node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", - "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", + "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.32.0" + } + }, + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", "dependencies": { - "hermes-parser": "0.25.1" + "hermes-estree": "0.32.0" } }, "node_modules/babel-plugin-transform-flow-enums": { @@ -4345,43 +4406,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/babel-preset-expo": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-13.2.1.tgz", - "integrity": "sha512-Ol3w0uLJNQ5tDfCf4L+IDTDMgJkVMQHhvYqMxs18Ib0DcaBQIfE8mneSSk7FcuI6FS0phw/rZhoEquQh1/Q3wA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/preset-react": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.79.4", - "babel-plugin-react-native-web": "~0.19.13", - "babel-plugin-syntax-hermes-parser": "^0.25.1", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4", - "react-refresh": "^0.14.2", - "resolve-from": "^5.0.0" - }, - "peerDependencies": { - "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" - }, - "peerDependenciesMeta": { - "babel-plugin-react-compiler": { - "optional": true - } - } - }, "node_modules/babel-preset-jest": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", @@ -4430,6 +4454,18 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -4496,12 +4532,13 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -4517,9 +4554,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "funding": [ { "type": "opencollective", @@ -4536,10 +4573,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4557,30 +4595,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4600,6 +4614,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -4618,6 +4633,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4631,6 +4647,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4643,39 +4660,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "license": "MIT", - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "license": "MIT", - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4696,9 +4680,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001720", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", - "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "funding": [ { "type": "opencollective", @@ -4741,15 +4725,6 @@ "node": ">=10" } }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/chrome-launcher": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", @@ -4848,15 +4823,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4939,6 +4905,19 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4961,16 +4940,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" }, @@ -5045,90 +5024,18 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", - "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "license": "MIT", "dependencies": { - "browserslist": "^4.25.0" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/cosmiconfig/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "license": "MIT", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -5165,15 +5072,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -5215,13 +5113,55 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -5287,9 +5227,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5303,7 +5243,14 @@ } } }, - "node_modules/decode-uri-component": { + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", @@ -5327,15 +5274,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5368,6 +5306,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -5394,6 +5333,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -5407,6 +5347,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5427,15 +5377,12 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-newline": { @@ -5458,18 +5405,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/dnssd-advertise": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.6.tgz", + "integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==", + "license": "MIT" }, "node_modules/doctrine": { "version": "3.0.0", @@ -5510,6 +5450,20 @@ ], "license": "BSD-2-Clause" }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", @@ -5539,37 +5493,11 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5593,9 +5521,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.161", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", - "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", "license": "ISC" }, "node_modules/emittery": { @@ -5626,6 +5554,20 @@ "node": ">= 0.8" } }, + "node_modules/enhanced-resolve": { + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -5638,19 +5580,11 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-editor": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", - "integrity": "sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -5738,6 +5672,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5784,6 +5719,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5866,6 +5802,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -5923,10 +5881,39 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", "bin": { @@ -5937,20 +5924,22 @@ } }, "node_modules/eslint-config-universe": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-universe/-/eslint-config-universe-12.1.0.tgz", - "integrity": "sha512-nAT0/rcOyYvsy0tY1yq0/BchUQImRCpORONzCvLkjixRBH5CbkMddb/V8ZlOLPgkfu3lp+sByRmSXNfoupTj2w==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-universe/-/eslint-config-universe-16.0.0.tgz", + "integrity": "sha512-T5DMGRJsE92dU/BeI4eAP0MRJnso5Sldm27Fx6Gsdcubwsn0gQopihzfLINegSfq/DrW/90Tdv+EqjraQyeE+Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-n": "^17.17.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0" + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "^16.0.0" }, "peerDependencies": { "eslint": ">=8.10", @@ -6032,6 +6021,28 @@ "eslint": ">=4.19.1" } }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, "node_modules/eslint-plugin-import": { "version": "2.31.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", @@ -6066,17 +6077,6 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -6101,9 +6101,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6113,6 +6113,59 @@ "node": "*" } }, + "node_modules/eslint-plugin-n": { + "version": "17.24.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", + "integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.5.0", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "globrex": "^0.1.2", + "ignore": "^5.3.2", + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -6134,21 +6187,10 @@ "eslint": ">=5.16.0" } }, - "node_modules/eslint-plugin-node/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint-plugin-node/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6223,27 +6265,40 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "hermes-estree": "0.25.1" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { @@ -6260,9 +6315,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6346,17 +6401,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -6374,9 +6418,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6494,12 +6538,6 @@ "node": ">=6" } }, - "node_modules/exec-async": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", - "integrity": "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==", - "license": "MIT" - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6558,28 +6596,34 @@ } }, "node_modules/expo": { - "version": "53.0.12", - "resolved": "https://registry.npmjs.org/expo/-/expo-53.0.12.tgz", - "integrity": "sha512-dtmED749hkxDWCcvtD++tb8bAm3Twv8qnUOXzVyXA5owNG0mwDIz0HveJTpWK1UzkY4HcTVRezDf0tflZJ+JXQ==", + "version": "55.0.28", + "resolved": "https://registry.npmjs.org/expo/-/expo-55.0.28.tgz", + "integrity": "sha512-rKWG+gjM4e+nJ6UUXn+jhs1+2lkzwLHjUZL9U4ejN3mLtqU9sOYvX925tOaD/lu3847LoZ8i5GJiyqu3tmK7Lg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.24.15", - "@expo/config": "~11.0.10", - "@expo/config-plugins": "~10.0.3", - "@expo/fingerprint": "0.13.1", - "@expo/metro-config": "0.20.15", - "@expo/vector-icons": "^14.0.0", - "babel-preset-expo": "~13.2.1", - "expo-asset": "~11.1.5", - "expo-constants": "~17.1.6", - "expo-file-system": "~18.1.10", - "expo-font": "~13.3.1", - "expo-keep-awake": "~14.1.4", - "expo-modules-autolinking": "2.1.12", - "expo-modules-core": "2.4.0", - "react-native-edge-to-edge": "1.6.0", - "whatwg-url-without-unicode": "8.0.0-3" + "@expo/cli": "55.0.34", + "@expo/config": "~55.0.19", + "@expo/config-plugins": "~55.0.11", + "@expo/devtools": "55.0.3", + "@expo/fingerprint": "0.16.8", + "@expo/local-build-cache-provider": "55.0.15", + "@expo/log-box": "55.0.13", + "@expo/metro": "~55.1.1", + "@expo/metro-config": "55.0.25", + "@expo/vector-icons": "^15.0.2", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~55.0.24", + "expo-asset": "~55.0.18", + "expo-constants": "~55.0.17", + "expo-file-system": "~55.0.24", + "expo-font": "~55.0.8", + "expo-keep-awake": "~55.0.8", + "expo-modules-autolinking": "55.0.25", + "expo-modules-core": "55.0.25", + "pretty-format": "^29.7.0", + "react-refresh": "^0.14.2", + "whatwg-url-minimum": "^0.1.2" }, "bin": { "expo": "bin/cli", @@ -6606,77 +6650,37 @@ } }, "node_modules/expo-application": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.1.5.tgz", - "integrity": "sha512-ToImFmzw8luY043pWFJhh2ZMm4IwxXoHXxNoGdlhD4Ym6+CCmkAvCglg0FK8dMLzAb+/XabmOE7Rbm8KZb6NZg==", + "version": "55.0.17", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-55.0.17.tgz", + "integrity": "sha512-ASuZe+Sl8ax+0pQOjd8L5cz5xpxW0F1VSorRKFr9LHxDSQwH929nYjbMrvW6KlRIIWZBIAQQsYqWiowytCLMRA==", "license": "MIT", "peerDependencies": { "expo": "*" } }, - "node_modules/expo-asset": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.1.5.tgz", - "integrity": "sha512-GEQDCqC25uDBoXHEnXeBuwpeXvI+3fRGvtzwwt0ZKKzWaN+TgeF8H7c76p3Zi4DfBMFDcduM0CmOvJX+yCCLUQ==", - "license": "MIT", - "dependencies": { - "@expo/image-utils": "^0.7.4", - "expo-constants": "~17.1.5" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-background-fetch": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/expo-background-fetch/-/expo-background-fetch-13.1.5.tgz", - "integrity": "sha512-tQiQEH68o6iduDDOKdlTx7oa1cWf8d+RsT4vm2dFzoSEIr1gJtdNojN4U2qBxwTGYttJI1ug5/PYKpQUIS5VmA==", - "license": "MIT", - "dependencies": { - "expo-task-manager": "~13.1.5" - }, - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-constants": { - "version": "17.1.7", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.7.tgz", - "integrity": "sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA==", + "version": "55.0.17", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.17.tgz", + "integrity": "sha512-t0SNWmXTjXPCHzjNsv1Okjaj8SpXQcUjDPtYDqvofDS+BhAsvlKNmwaaWXvduBjjmmmJWNH8q1/x9IWQ+upg8g==", "license": "MIT", "dependencies": { - "@expo/config": "~11.0.12", - "@expo/env": "~1.0.7" + "@expo/env": "~2.1.3" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, - "node_modules/expo-device": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-7.1.4.tgz", - "integrity": "sha512-HS04IiE1Fy0FRjBLurr9e5A6yj3kbmQB+2jCZvbSGpsjBnCLdSk/LCii4f5VFhPIBWJLyYuN5QqJyEAw6BcS4Q==", - "license": "MIT", - "dependencies": { - "ua-parser-js": "^0.7.33" - }, - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-eas-client": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.14.3.tgz", - "integrity": "sha512-BW2mSNEjFRFC8/CbkMQ3mfVhBdeZIjZhNfncw7PP80xEptLWhVjGTqwG8Usi0/yPpIu/YNYgop+XGMfhXyh9uA==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-55.0.5.tgz", + "integrity": "sha512-wRagCeSbSnSGVXgP7V+qiGfXzZ9hTVKWvKIOP7lwrX3MIEenNmNlO4D3RVC3aNU2GhmO3ZCZIIEre80KZoUUHA==", "license": "MIT" }, "node_modules/expo-file-system": { - "version": "18.1.10", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.1.10.tgz", - "integrity": "sha512-SyaWg+HitScLuyEeSG9gMSDT0hIxbM9jiZjSBP9l9zMnwZjmQwsusE6+7qGiddxJzdOhTP4YGUfvEzeeS0YL3Q==", + "version": "55.0.24", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.24.tgz", + "integrity": "sha512-7/HJdvaaf3kP5T3atd+6Q0/QexrGio4Fs2GVtp7G8d/xQCSu01yeGReu7tGQo9VAM67Snq+ujGlL8q2wbMXLkQ==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -6684,66 +6688,32 @@ } }, "node_modules/expo-font": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.3.1.tgz", - "integrity": "sha512-d+xrHYvSM9WB42wj8vP9OOFWyxed5R1evphfDb6zYBmC1dA9Hf89FpT7TNFtj2Bk3clTnpmVqQTCYbbA2P3CLg==", + "version": "55.0.8", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.8.tgz", + "integrity": "sha512-WyP75pnKqhLNktYwDn3xKAUNt5rLihRDv8XWGhhz6VEhVqypixpT86NA3uGtiDTlM3gGjhrYCY7o7ypXgCUOZg==", "license": "MIT", "dependencies": { "fontfaceobserver": "^2.1.0" }, - "peerDependencies": { - "expo": "*", - "react": "*" - } - }, - "node_modules/expo-haptics": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-14.1.4.tgz", - "integrity": "sha512-QZdE3NMX74rTuIl82I+n12XGwpDWKb8zfs5EpwsnGi/D/n7O2Jd4tO5ivH+muEG/OCJOMq5aeaVDqqaQOhTkcA==", - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-health-connect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expo-health-connect/-/expo-health-connect-0.1.1.tgz", - "integrity": "sha512-9URHckKOHYt7OvwwTis6TNo9XhV8KivkwARexgQ9KAyLWH8T+jCLI3kd7fhCm0WWp3IyyKit362oX7F1l6JLHA==", - "license": "MIT", "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - } } }, "node_modules/expo-json-utils": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz", - "integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==", + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-55.0.2.tgz", + "integrity": "sha512-QJMOZOPOG7CTnKcrdVaiummn2va1MCO56z++eyWkDv3GBRODldM6MFMDf/jTREWthFc2Nxo6TuyWRrEV9S6n/Q==", "license": "MIT" }, - "node_modules/expo-keep-awake": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.1.4.tgz", - "integrity": "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*" - } - }, "node_modules/expo-linking": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.5.tgz", - "integrity": "sha512-8g20zOpROW78bF+bLI4a3ZWj4ntLgM0rCewKycPL0jk9WGvBrBtFtwwADJgOiV1EurNp3lcquerXGlWS+SOQyA==", + "version": "55.0.16", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-55.0.16.tgz", + "integrity": "sha512-O+Idexholc5rlr6Z4W/+TewTLQVnbEXztoLgLEbJwh5/A1SsJ+eq9yGGPwd4rcQ+fEBDiExr6qZxbUArnUGHfw==", "license": "MIT", "dependencies": { - "expo-constants": "~17.1.6", + "expo-constants": "~55.0.16", "invariant": "^2.2.4" }, "peerDependencies": { @@ -6751,68 +6721,63 @@ "react-native": "*" } }, - "node_modules/expo-location": { - "version": "18.1.5", - "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-18.1.5.tgz", - "integrity": "sha512-/ugxS4Ort9DbKD0Do6xn4TjeHaA/by5vv0p0PrY5Zbcez1S0AfrQn/4JG/8PLT2imngMwZU9TDKrgxqJQ4RuPQ==", - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-manifests": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.16.5.tgz", - "integrity": "sha512-zLUeJogn2C7qOE75Zz7jcmJorMfIbSRR35ctspN0OK/Hq/+PAAptA8p9jNVC8xp/91uP9uI8f3xPhh+A11eR2A==", + "version": "55.0.19", + "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-55.0.19.tgz", + "integrity": "sha512-NmIoGapFzTTZhR46loLqTTnZfL8C6GQdQA47q1NjWiT3UeUPrGUrK7CEASMdhCFxaymkpbRFsbMECeSq6knIPg==", "license": "MIT", "dependencies": { - "@expo/config": "~11.0.10", - "expo-json-utils": "~0.15.0" + "expo-json-utils": "~55.0.2" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-modules-autolinking": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.12.tgz", - "integrity": "sha512-rW5YSW66pUx1nLqn7TO0eWRnP4LDvySW1Tom0wjexk3Tx/upg9LYE5tva7p5AX/cdFfiZcEqPcOxP4RyT++Xlg==", + "version": "55.0.25", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.25.tgz", + "integrity": "sha512-qO8se6zTxfdCGeuwc0dCTmnstW5r1tKSr9SoWtZn0mhlr5Qe5RSJa0vhzQ32eIwgMd77GJrc1yJtkktgN817vg==", "license": "MIT", "dependencies": { + "@expo/require-utils": "^55.0.6", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", - "commander": "^7.2.0", - "find-up": "^5.0.0", - "glob": "^10.4.2", - "require-from-string": "^2.0.2", - "resolve-from": "^5.0.0" + "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "node_modules/expo-modules-core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.4.0.tgz", - "integrity": "sha512-Ko5eHBdvuMykjw9P9C9PF54/wBSsGOxaOjx92I5BwgKvEmUwN3UrXFV4CXzlLVbLfSYUQaLcB220xmPfgvT7Fg==", + "version": "55.0.25", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.25.tgz", + "integrity": "sha512-yXpfg7aHLbuqoXocK34Vua6Aey5SCyqLygAsXAMbul9P8vfBjLpaOPiTJ5cLVF7Drfq8ownqVJO6qpGEtZ6GOw==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-worklets": "^0.7.4 || ^0.8.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } } }, "node_modules/expo-notifications": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.31.4.tgz", - "integrity": "sha512-NnGKIFGpgZU66qfiFUyjEBYsS77VahURpSSeWEOLt+P1zOaUFlgx2XqS+dxH3/Bn1Vm7TMj04qKsK5KvzR/8Lw==", + "version": "55.0.25", + "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-55.0.25.tgz", + "integrity": "sha512-yCbZElqHLvGommwMXJn0ZS/8kJ8wbf7Ujn5or4lVGthRUhaRZfTrN/UJlc9FnQmuKJ35I4rSzo1fqeQjxb+XOw==", "license": "MIT", "dependencies": { - "@expo/image-utils": "^0.7.6", - "@ide/backoff": "^1.0.0", + "@expo/image-utils": "^0.8.15", "abort-controller": "^3.0.0", - "assert": "^2.0.0", "badgin": "^1.1.5", - "expo-application": "~6.1.5", - "expo-constants": "~17.1.7" + "expo-application": "~55.0.17", + "expo-constants": "~55.0.17" }, "peerDependencies": { "expo": "*", @@ -6820,36 +6785,38 @@ "react-native": "*" } }, - "node_modules/expo-sensors": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/expo-sensors/-/expo-sensors-14.1.4.tgz", - "integrity": "sha512-KHROi5C8dhXedMwx7fZ5eyv9p382F5XOIex4a+GpdOTL3OY4xyk08kt7x64FtMeeoT87gYD3mb9LrBpHyNubkg==", + "node_modules/expo-server": { + "version": "55.0.11", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.11.tgz", + "integrity": "sha512-AxRdHqcv0H1g4s923vu+5n1Nrhne23bjXbP+Vl7+Lwfpe7MG9PuU1IS95IJK6a+7BVV1mRN6QlZvs8Yv7EEXNQ==", "license": "MIT", - "dependencies": { - "invariant": "^2.2.4" - }, - "peerDependencies": { - "expo": "*", - "react-native": "*" + "engines": { + "node": ">=20.16.0" } }, "node_modules/expo-sharing": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-13.1.5.tgz", - "integrity": "sha512-X/5sAEiWXL2kdoGE3NO5KmbfcmaCWuWVZXHu8OQef7Yig4ZgHFkGD11HKJ5KqDrDg+SRZe4ISd6MxE7vGUgm4w==", + "version": "55.0.22", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-55.0.22.tgz", + "integrity": "sha512-52s4FfjNMLJfKl4wxakRjbrfgya3w7XZ15tCc/7IVH2HX/q467mePBS1VqYWbuuKDjvDAL02Aj2LcFaUNwkoWQ==", "license": "MIT", + "dependencies": { + "@expo/config-plugins": "^55.0.11", + "@expo/config-types": "^55.0.6", + "@expo/plist": "^0.5.4" + }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react": "*", + "react-native": "*" } }, "node_modules/expo-status-bar": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.2.3.tgz", - "integrity": "sha512-+c8R3AESBoduunxTJ8353SqKAKpxL6DvcD8VKBuh81zzJyUUbfB4CVjr1GufSJEKsMzNPXZU+HJwXx7Xh7lx8Q==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-55.0.6.tgz", + "integrity": "sha512-ijOUptfdiqYt7rObZ6jrPQ8sE5YN/8MxKCIJx0b7TY4nGkSJxhPIxeoW4GXcXCA8mTQ9PiOHH/ThLZgRVZvUlQ==", "license": "MIT", "dependencies": { - "react-native-edge-to-edge": "1.6.0", - "react-native-is-edge-to-edge": "^1.1.6" + "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", @@ -6857,41 +6824,29 @@ } }, "node_modules/expo-structured-headers": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-4.1.0.tgz", - "integrity": "sha512-2X+aUNzC/qaw7/WyUhrVHNDB0uQ5rE12XA2H/rJXaAiYQSuOeU90ladaN0IJYV9I2XlhYrjXLktLXWbO7zgbag==", + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-55.0.2.tgz", + "integrity": "sha512-KITovrWigTOtsII5hRQ9/3ydaNcxCux5g6O+eTPLyjnye9dpkDKl5GmCLVPVKIL/d7253OtbGtWMD4m0gha5pw==", "license": "MIT" }, - "node_modules/expo-task-manager": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/expo-task-manager/-/expo-task-manager-13.1.5.tgz", - "integrity": "sha512-9jspZZ6y6hwo6QBwmfsUUX6sY4Mnrw8170LnOqrVfRuClkGaMrqDeBa9YUFTDt/BNYZ/3/QlZZ5QUHT9ziROfQ==", - "license": "MIT", - "dependencies": { - "unimodules-app-loader": "~5.1.3" - }, - "peerDependencies": { - "expo": "*", - "react-native": "*" - } - }, "node_modules/expo-updates": { - "version": "0.28.15", - "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.28.15.tgz", - "integrity": "sha512-JwmZ5RP7sPUgl+7FoP62Qs1gfkpYzJGhc6LfAiVSK9pxV0PWNP1jxzE9Jp4GSm/Ya9Tbz0mjDYaZPwoUGcUd+g==", + "version": "55.0.26", + "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-55.0.26.tgz", + "integrity": "sha512-Ham6Knj1YNQBJxgz8sO/VwaHwNRPl4VM5X8vajO2Exfd6LpoIXCXy4pTAJYC8eaTiM47yFZV+nuNXXO0DokefQ==", "license": "MIT", "dependencies": { - "@expo/code-signing-certificates": "0.0.5", - "@expo/config": "~11.0.10", - "@expo/config-plugins": "~10.0.3", + "@expo/code-signing-certificates": "^0.0.6", + "@expo/plist": "^0.5.4", "@expo/spawn-async": "^1.7.2", - "arg": "4.1.0", + "arg": "^4.1.0", "chalk": "^4.1.2", - "expo-eas-client": "~0.14.3", - "expo-manifests": "~0.16.5", - "expo-structured-headers": "~4.1.0", - "expo-updates-interface": "~1.1.0", - "glob": "^10.4.2", + "debug": "^4.3.4", + "expo-eas-client": "~55.0.5", + "expo-manifests": "~55.0.19", + "expo-structured-headers": "~55.0.2", + "expo-updates-interface": "~55.1.6", + "getenv": "^2.0.0", + "glob": "^13.0.0", "ignore": "^5.3.1", "resolve-from": "^5.0.0" }, @@ -6900,13 +6855,14 @@ }, "peerDependencies": { "expo": "*", - "react": "*" + "react": "*", + "react-native": "*" } }, "node_modules/expo-updates-interface": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.1.0.tgz", - "integrity": "sha512-DeB+fRe0hUDPZhpJ4X4bFMAItatFBUPjw/TVSbJsaf3Exeami+2qbbJhWkcTMoYHOB73nOIcaYcWXYJnCJXO0w==", + "version": "55.1.6", + "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-55.1.6.tgz", + "integrity": "sha512-evxNpagCkjT3lE6bGV570TFzRtKuIuLY8I37RYHoriXCJ+ZKCN1hbmklK29uAixya+BxGpeTI2K4FqYeJLvfrw==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -6918,10 +6874,272 @@ "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", "license": "MIT" }, + "node_modules/expo/node_modules/@expo/cli": { + "version": "55.0.34", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.34.tgz", + "integrity": "sha512-b+PnIWiIUxXmSi09hmbzeBNlygP/W1uuRQU6cm6ke9P5sBEknbZ4cpBY0Xz4L1L3Pr6u/BUbIW0KXJBOppzAww==", + "license": "MIT", + "dependencies": { + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~55.0.19", + "@expo/config-plugins": "~55.0.11", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.1.3", + "@expo/image-utils": "^0.8.15", + "@expo/json-file": "^10.0.15", + "@expo/log-box": "55.0.13", + "@expo/metro": "~55.1.1", + "@expo/metro-config": "~55.0.25", + "@expo/osascript": "^2.4.4", + "@expo/package-manager": "^1.10.6", + "@expo/plist": "^0.5.4", + "@expo/prebuild-config": "^55.0.20", + "@expo/require-utils": "^55.0.6", + "@expo/router-server": "^55.0.18", + "@expo/schema-utils": "^55.0.5", + "@expo/spawn-async": "^1.7.2", + "@expo/ws-tunnel": "^1.0.1", + "@expo/xcpretty": "^4.4.0", + "@react-native/dev-middleware": "0.83.10", + "accepts": "^1.3.8", + "arg": "^5.0.2", + "better-opn": "~3.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "dnssd-advertise": "^1.1.6", + "expo-server": "^55.0.11", + "fetch-nodeshim": "^0.4.10", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "multitars": "^1.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^4.0.3", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "resolve-from": "^5.0.0", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "source-map-support": "~0.5.21", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "terminal-link": "^2.1.1", + "toqr": "^0.1.1", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1", + "zod": "^3.25.76" + }, + "bin": { + "expo-internal": "build/bin/cli" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { + "version": "55.0.18", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.18.tgz", + "integrity": "sha512-W0VsvIiR48OvdlAOUlag4qspGYT/DV4srfYowlbYxwZh5Qw0MjiZAID4Zt7F0qynGZZxx8OZPpFhIX7XsqtRmg==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "@expo/metro-runtime": "^55.0.11", + "expo": "*", + "expo-constants": "^55.0.16", + "expo-font": "^55.0.8", + "expo-router": "*", + "expo-server": "^55.0.11", + "react": "*", + "react-dom": "*", + "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + }, + "peerDependenciesMeta": { + "@expo/metro-runtime": { + "optional": true + }, + "expo-router": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/json-file": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.2.0.tgz", + "integrity": "sha512-S6XzKe3R9GQeHiUPXc3xJjOv2VJhOEwFYf7xdC2z2cUqt3kZJ9mSO877sNQloVdnW/SUCtPY3bexlM7nwq+CAQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "json5": "^2.2.3" + } + }, + "node_modules/expo/node_modules/@expo/log-box": { + "version": "55.0.13", + "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.13.tgz", + "integrity": "sha512-pV623uwyKjw/L1HVWOpwWOu/ISLH1+c+ESVv30alQMbEaE3cLcwcQ+UnHiAGayMBNMQwK57eckOgH40RBXHfCA==", + "license": "MIT", + "dependencies": { + "@expo/dom-webview": "^55.0.6", + "anser": "^1.4.9", + "stacktrace-parser": "^0.1.10" + }, + "peerDependencies": { + "@expo/dom-webview": "^55.0.6", + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/@expo/log-box/node_modules/@expo/dom-webview": { + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.6.tgz", + "integrity": "sha512-ZNm8tiNEZysxrr36J0x4mOCGyJDcaIvL/3tMxBz0VJIJDcV19xjuJAhJQxHovu+jKx6s9tRyEAINa1mdrzV39g==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.1.tgz", + "integrity": "sha512-HgErPZTghW76Rkq9uqn5ESeiD97FbqpZ1V170T1RG2RDp+7pJVQV2pQJs7y5YzN0/gcT6GM5ci9apRnIwuyPdQ==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.32.1" + } + }, + "node_modules/expo/node_modules/babel-preset-expo": { + "version": "55.0.24", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.24.tgz", + "integrity": "sha512-dzjg70cq3Ls5msL79GSktjqtbjzXh/r5errxz8aD97S180pkXrMga22ozrcNhHddMBtZGDKN2jBK1FS1RLkfBQ==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.20.5", + "@babel/helper-module-imports": "^7.25.9", + "@babel/plugin-proposal-decorators": "^7.12.9", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", + "@react-native/babel-preset": "0.83.10", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "expo-widgets": "^55.0.20", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + }, + "expo-widgets": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/expo-asset": { + "version": "55.0.18", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.18.tgz", + "integrity": "sha512-dbNMcBE0AaFbdxjBSRgDLdtOi8PHt9CQOg+J3EdeXNPKZCDO88kvaHcXaYUMmkbN2KJYQlfu1GxfUVAIoi7iVQ==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.8.15", + "expo-constants": "~55.0.17" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/expo-keep-awake": { + "version": "55.0.8", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.8.tgz", + "integrity": "sha512-PfIpMfM+STOBwkR5XOE+yVtER86c44MD+W8QD8JxuO0sT9pF7Y1SJYakWlpvX8xsGA+bjKLxftm9403s9kQhKA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, + "node_modules/expo/node_modules/hermes-estree": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", + "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", + "license": "MIT" + }, + "node_modules/expo/node_modules/hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", + "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.1" + } + }, + "node_modules/expo/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/exponential-backoff": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", - "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, "node_modules/fast-deep-equal": { @@ -6937,36 +7155,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6990,14 +7178,50 @@ "reusify": "^1.0.4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-nodeshim": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", + "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "6.0.1", @@ -7070,6 +7294,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7098,9 +7323,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -7120,6 +7345,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -7147,13 +7373,21 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/freeport-async": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz", - "integrity": "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, "node_modules/fresh": { @@ -7247,6 +7481,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7280,6 +7515,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7320,30 +7556,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/getenv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", - "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", + "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7362,28 +7608,17 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -7403,31 +7638,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7475,6 +7697,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -7503,6 +7726,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7515,6 +7739,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -7527,9 +7752,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7538,19 +7763,25 @@ "node": ">= 0.4" } }, + "node_modules/hermes-compiler": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.1.tgz", + "integrity": "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA==", + "license": "MIT" + }, "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", "license": "MIT" }, "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", "dependencies": { - "hermes-estree": "0.25.1" + "hermes-estree": "0.35.0" } }, "node_modules/hoist-non-react-statics": { @@ -7586,6 +7817,19 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -7594,30 +7838,62 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-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==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -7641,25 +7917,18 @@ "node": ">=10.17.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/ignore": { "version": "5.3.2", @@ -7758,12 +8027,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -7788,22 +8051,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -7826,6 +8073,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-async-function": { @@ -7885,6 +8133,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7943,15 +8192,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -7993,6 +8233,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -8007,6 +8256,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -8047,22 +8297,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -8121,10 +8355,18 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8220,6 +8462,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -8572,17 +8815,6 @@ } } }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8606,9 +8838,9 @@ } }, "node_modules/jest-config/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -8664,6 +8896,34 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -8681,6 +8941,42 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-expo": { + "version": "55.0.20", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-55.0.20.tgz", + "integrity": "sha512-55NG6HFyFIViAo3eMWAidFTVPN8nTZU8b5Ed1YtNtYYA6aWmqa72fvHLP/phQdYaxIjFP2wdm1JO/+xEf5CdGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expo/config": "~55.0.19", + "@expo/json-file": "^10.0.15", + "@jest/create-cache-key-function": "^29.2.1", + "@jest/globals": "^29.2.1", + "babel-jest": "^29.2.1", + "jest-environment-jsdom": "^29.2.1", + "jest-snapshot": "^29.2.1", + "jest-watch-select-projects": "^2.0.0", + "jest-watch-typeahead": "2.2.1", + "json5": "^2.2.3", + "lodash": "^4.17.19", + "react-test-renderer": "19.2.0", + "server-only": "^0.0.1", + "stacktrace-js": "^2.0.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*", + "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" + }, + "peerDependenciesMeta": { + "react-server-dom-webpack": { + "optional": true + } + } + }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -8914,20 +9210,9 @@ "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runtime/node_modules/glob": { @@ -8953,9 +9238,9 @@ } }, "node_modules/jest-runtime/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -9028,9 +9313,9 @@ } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -9068,6 +9353,136 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-watch-select-projects": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jest-watch-select-projects/-/jest-watch-select-projects-2.0.0.tgz", + "integrity": "sha512-j00nW4dXc2NiCW6znXgFLF9g8PJ0zP25cpQ1xRro/HU2GBfZQFZD0SoXnAlaoKkIY4MlfTMkKGbNXFpvCdjl1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "chalk": "^3.0.0", + "prompts": "^2.2.1" + } + }, + "node_modules/jest-watch-select-projects/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.1.tgz", + "integrity": "sha512-jYpYmUnTzysmVnwq49TAxlmtOAwp8QIqvZyoofQFn8fiWhEDZj33ZXzg3JA4nGnzWFm1hbWf3ADpteUokvXgFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^6.0.0", + "chalk": "^4.0.0", + "jest-regex-util": "^29.0.0", + "jest-watcher": "^29.0.0", + "slash": "^5.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -9131,9 +9546,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9148,6 +9573,79 @@ "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", "license": "0BSD" }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/jsdom/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -9167,12 +9665,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -9242,9 +9734,9 @@ } }, "node_modules/lan-network": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.1.7.tgz", - "integrity": "sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.1.tgz", + "integrity": "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==", "license": "MIT", "bin": { "lan-network": "dist/lan-network-cli.js" @@ -9299,12 +9791,12 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz", - "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { - "detect-libc": "^1.0.3" + "detect-libc": "^2.0.3" }, "engines": { "node": ">= 12.0.0" @@ -9314,22 +9806,43 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.27.0", - "lightningcss-darwin-x64": "1.27.0", - "lightningcss-freebsd-x64": "1.27.0", - "lightningcss-linux-arm-gnueabihf": "1.27.0", - "lightningcss-linux-arm64-gnu": "1.27.0", - "lightningcss-linux-arm64-musl": "1.27.0", - "lightningcss-linux-x64-gnu": "1.27.0", - "lightningcss-linux-x64-musl": "1.27.0", - "lightningcss-win32-arm64-msvc": "1.27.0", - "lightningcss-win32-x64-msvc": "1.27.0" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz", - "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -9347,9 +9860,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", - "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -9367,9 +9880,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", - "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -9387,9 +9900,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", - "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -9407,9 +9920,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", - "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -9427,9 +9940,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", - "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -9447,9 +9960,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", - "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -9467,9 +9980,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", - "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -9487,9 +10000,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", - "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -9507,9 +10020,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", - "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -9530,12 +10043,14 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9548,9 +10063,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -9724,6 +10239,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9759,56 +10275,45 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/metro": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.82.4.tgz", - "integrity": "sha512-/gFmw3ux9CPG5WUmygY35hpyno28zi/7OUn6+OFfbweA8l0B+PPqXXLr0/T6cf5nclCcH0d22o+02fICaShVxw==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.7.tgz", + "integrity": "sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", + "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.3", - "@babel/types": "^7.25.2", - "accepts": "^1.3.7", - "chalk": "^4.0.0", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.28.1", + "hermes-parser": "0.35.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.82.4", - "metro-cache": "0.82.4", - "metro-cache-key": "0.82.4", - "metro-config": "0.82.4", - "metro-core": "0.82.4", - "metro-file-map": "0.82.4", - "metro-resolver": "0.82.4", - "metro-runtime": "0.82.4", - "metro-source-map": "0.82.4", - "metro-symbolicate": "0.82.4", - "metro-transform-plugins": "0.82.4", - "metro-transform-worker": "0.82.4", - "mime-types": "^2.1.27", + "metro-babel-transformer": "0.83.7", + "metro-cache": "0.83.7", + "metro-cache-key": "0.83.7", + "metro-config": "0.83.7", + "metro-core": "0.83.7", + "metro-file-map": "0.83.7", + "metro-resolver": "0.83.7", + "metro-runtime": "0.83.7", + "metro-source-map": "0.83.7", + "metro-symbolicate": "0.83.7", + "metro-transform-plugins": "0.83.7", + "metro-transform-worker": "0.83.7", + "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", @@ -9820,103 +10325,89 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-babel-transformer": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.82.4.tgz", - "integrity": "sha512-4juJahGRb1gmNbQq48lNinB6WFNfb6m0BQqi/RQibEltNiqTCxew/dBspI2EWA4xVCd3mQWGfw0TML4KurQZnQ==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.7.tgz", + "integrity": "sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.28.1", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.83.7", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18.18" - } - }, - "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.28.1.tgz", - "integrity": "sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==", - "license": "MIT" - }, - "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.28.1.tgz", - "integrity": "sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.28.1" + "node": ">=20.19.4" } }, "node_modules/metro-cache": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.82.4.tgz", - "integrity": "sha512-vX0ylSMGtORKiZ4G8uP6fgfPdDiCWvLZUGZ5zIblSGylOX6JYhvExl0Zg4UA9pix/SSQu5Pnp9vdODMFsNIxhw==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.7.tgz", + "integrity": "sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", - "metro-core": "0.82.4" + "metro-core": "0.83.7" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-cache-key": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.82.4.tgz", - "integrity": "sha512-2JCTqcpF+f2OghOpe/+x+JywfzDkrHdAqinPFWmK2ezNAU/qX0jBFaTETogPibFivxZJil37w9Yp6syX8rFUng==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.7.tgz", + "integrity": "sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-config": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.82.4.tgz", - "integrity": "sha512-Ki3Wumr3hKHGDS7RrHsygmmRNc/PCJrvkLn0+BWWxmbOmOcMMJDSmSI+WRlT8jd5VPZFxIi4wg+sAt5yBXAK0g==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.7.tgz", + "integrity": "sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q==", "license": "MIT", "dependencies": { "connect": "^3.6.5", - "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", - "metro": "0.82.4", - "metro-cache": "0.82.4", - "metro-core": "0.82.4", - "metro-runtime": "0.82.4" + "metro": "0.83.7", + "metro-cache": "0.83.7", + "metro-core": "0.83.7", + "metro-runtime": "0.83.7", + "yaml": "^2.6.1" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-core": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.82.4.tgz", - "integrity": "sha512-Xo4ozbxPg2vfgJGCgXZ8sVhC2M0lhTqD+tsKO2q9aelq/dCjnnSb26xZKcQO80CQOQUL7e3QWB7pLFGPjZm31A==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.7.tgz", + "integrity": "sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.82.4" + "metro-resolver": "0.83.7" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-file-map": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.82.4.tgz", - "integrity": "sha512-eO7HD1O3aeNsbEe6NBZvx1lLJUrxgyATjnDmb7bm4eyF6yWOQot9XVtxTDLNifECuvsZ4jzRiTInrbmIHkTdGA==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.7.tgz", + "integrity": "sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -9930,66 +10421,65 @@ "walker": "^1.0.7" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-minify-terser": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.82.4.tgz", - "integrity": "sha512-W79Mi6BUwWVaM8Mc5XepcqkG+TSsCyyo//dmTsgYfJcsmReQorRFodil3bbJInETvjzdnS1mCsUo9pllNjT1Hg==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.7.tgz", + "integrity": "sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-resolver": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.82.4.tgz", - "integrity": "sha512-uWoHzOBGQTPT5PjippB8rRT3iI9CTgFA9tRiLMzrseA5o7YAlgvfTdY9vFk2qyk3lW3aQfFKWkmqENryPRpu+Q==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.7.tgz", + "integrity": "sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-runtime": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.82.4.tgz", - "integrity": "sha512-vVyFO7H+eLXRV2E7YAUYA7aMGBECGagqxmFvC2hmErS7oq90BbPVENfAHbUWq1vWH+MRiivoRxdxlN8gBoF/dw==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.7.tgz", + "integrity": "sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-source-map": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.82.4.tgz", - "integrity": "sha512-9jzDQJ0FPas1FuQFtwmBHsez2BfhFNufMowbOMeG3ZaFvzeziE8A0aJwILDS3U+V5039ssCQFiQeqDgENWvquA==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.7.tgz", + "integrity": "sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.3", - "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", - "@babel/types": "^7.25.2", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.82.4", + "metro-symbolicate": "0.83.7", "nullthrows": "^1.1.1", - "ob1": "0.82.4", + "ob1": "0.83.7", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-source-map/node_modules/source-map": { @@ -10002,14 +10492,14 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.82.4.tgz", - "integrity": "sha512-LwEwAtdsx7z8rYjxjpLWxuFa2U0J6TS6ljlQM4WAATKa4uzV8unmnRuN2iNBWTmRqgNR77mzmI2vhwD4QSCo+w==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.7.tgz", + "integrity": "sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.82.4", + "metro-source-map": "0.83.7", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" @@ -10018,7 +10508,7 @@ "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-symbolicate/node_modules/source-map": { @@ -10031,44 +10521,57 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.82.4.tgz", - "integrity": "sha512-NoWQRPHupVpnDgYguiEcm7YwDhnqW02iWWQjO2O8NsNP09rEMSq99nPjARWfukN7+KDh6YjLvTIN20mj3dk9kw==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.7.tgz", + "integrity": "sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.3", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-transform-worker": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.82.4.tgz", - "integrity": "sha512-kPI7Ad/tdAnI9PY4T+2H0cdgGeSWWdiPRKuytI806UcN4VhFL6OmYa19/4abYVYF+Cd2jo57CDuwbaxRfmXDhw==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.7.tgz", + "integrity": "sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", - "@babel/types": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", - "metro": "0.82.4", - "metro-babel-transformer": "0.82.4", - "metro-cache": "0.82.4", - "metro-cache-key": "0.82.4", - "metro-minify-terser": "0.82.4", - "metro-source-map": "0.82.4", - "metro-transform-plugins": "0.82.4", + "metro": "0.83.7", + "metro-babel-transformer": "0.83.7", + "metro-cache": "0.83.7", + "metro-cache-key": "0.83.7", + "metro-minify-terser": "0.83.7", + "metro-source-map": "0.83.7", + "metro-transform-plugins": "0.83.7", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" + } + }, + "node_modules/metro/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" } }, "node_modules/metro/node_modules/ci-info": { @@ -10077,19 +10580,29 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "license": "MIT" }, - "node_modules/metro/node_modules/hermes-estree": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.28.1.tgz", - "integrity": "sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==", - "license": "MIT" - }, - "node_modules/metro/node_modules/hermes-parser": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.28.1.tgz", - "integrity": "sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==", + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "hermes-estree": "0.28.1" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/metro/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/metro/node_modules/source-map": { @@ -10102,9 +10615,9 @@ } }, "node_modules/metro/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.12.tgz", + "integrity": "sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -10136,9 +10649,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -10200,50 +10713,60 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "license": "ISC", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -10262,21 +10785,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } + "node_modules/multitars": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multitars/-/multitars-1.0.0.tgz", + "integrity": "sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==", + "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -10307,16 +10825,10 @@ "node": ">= 0.6" } }, - "node_modules/nested-error-stacks": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz", - "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", - "license": "MIT" - }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -10329,10 +10841,13 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT" + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -10359,9 +10874,9 @@ } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10401,22 +10916,30 @@ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", "license": "MIT" }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, "node_modules/ob1": { - "version": "0.82.4", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.82.4.tgz", - "integrity": "sha512-n9S8e4l5TvkrequEAMDidl4yXesruWTNTzVkeaHSGywoTOIwTzZzKw7Z670H3eaXDZui5MJXjWGNzYowVZIxCA==", + "version": "0.83.7", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.7.tgz", + "integrity": "sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10435,26 +10958,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10464,6 +10972,7 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10562,9 +11071,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" @@ -10760,6 +11269,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10775,6 +11285,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10845,6 +11356,32 @@ "node": ">=10" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10888,35 +11425,28 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": "20 || >=22" } }, "node_modules/paths-js": { @@ -10935,12 +11465,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -11057,15 +11587,16 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -11082,7 +11613,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -11129,18 +11660,6 @@ "node": ">=6.0.0" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -11232,10 +11751,24 @@ "dev": true, "license": "MIT" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11258,14 +11791,6 @@ ], "license": "MIT" }, - "node_modules/qrcode-terminal": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", - "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==", - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -11284,6 +11809,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -11323,43 +11855,19 @@ "node": ">= 0.6" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "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.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "license": "MIT", "dependencies": { "shell-quote": "^1.6.1", @@ -11367,9 +11875,9 @@ } }, "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.12.tgz", + "integrity": "sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -11400,63 +11908,62 @@ } }, "node_modules/react-is": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", - "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", "license": "MIT" }, "node_modules/react-native": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.4.tgz", - "integrity": "sha512-CfxYMuszvnO/33Q5rB//7cU1u9P8rSOvzhE2053Phdb8+6bof9NLayCllU2nmPrm8n9o6RU1Fz5H0yquLQ0DAw==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.10.tgz", + "integrity": "sha512-4gbmtAyfC0F4jU2hl/l/HZbRIfRl0d5RdHNpJLf7sT/0zhYDEhiFcFk7ii2utl7O8BIHRUmGe/anfWP3DNPTZw==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.79.4", - "@react-native/codegen": "0.79.4", - "@react-native/community-cli-plugin": "0.79.4", - "@react-native/gradle-plugin": "0.79.4", - "@react-native/js-polyfills": "0.79.4", - "@react-native/normalize-colors": "0.79.4", - "@react-native/virtualized-lists": "0.79.4", + "@react-native/assets-registry": "0.83.10", + "@react-native/codegen": "0.83.10", + "@react-native/community-cli-plugin": "0.83.10", + "@react-native/gradle-plugin": "0.83.10", + "@react-native/js-polyfills": "0.83.10", + "@react-native/normalize-colors": "0.83.10", + "@react-native/virtualized-lists": "0.83.10", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", - "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", - "chalk": "^4.0.0", "commander": "^12.0.0", - "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", + "hermes-compiler": "0.14.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.82.0", - "metro-source-map": "^0.82.0", + "metro-runtime": "^0.83.6", + "metro-source-map": "^0.83.6", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", - "react-devtools-core": "^6.1.1", + "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.25.0", + "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.3", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "react-native": "cli.js" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^19.0.0", - "react": "^19.0.0" + "@types/react": "^19.1.1", + "react": "^19.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -11480,20 +11987,10 @@ "react-native-svg": "> 6.4.1" } }, - "node_modules/react-native-edge-to-edge": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/react-native-edge-to-edge/-/react-native-edge-to-edge-1.6.0.tgz", - "integrity": "sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-gesture-handler": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.24.0.tgz", - "integrity": "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.1.tgz", + "integrity": "sha512-xIUBDo5ktmJs++0fZlavQNvDEE4PsihWhSeJsJtoz4Q6p0MiTM9TgrTgfEgzRR36qGPytFoeq+ShLrVwGdpUdA==", "license": "MIT", "dependencies": { "@egjs/hammerjs": "^2.0.17", @@ -11505,281 +12002,238 @@ "react-native": "*" } }, - "node_modules/react-native-health": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/react-native-health/-/react-native-health-1.19.0.tgz", - "integrity": "sha512-IeF/YYWDKBkx3R89uk/zdF3Sql9Jj+okZBXTAC62FHE+Ef3CMN+ArL6D1SzFk/dc0qK+Q7mnZrmFDbTsftszxw==", + "node_modules/react-native-is-edge-to-edge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", + "integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==", "license": "MIT", - "dependencies": { - "@expo/config-plugins": "^7.2.2" - }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { - "react-native": ">=0.67.3" - } - }, - "node_modules/react-native-health/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" + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/@expo/config-plugins": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-7.9.2.tgz", - "integrity": "sha512-sRU/OAp7kJxrCUiCTUZqvPMKPdiN1oTmNfnbkG4oPdfWQTpid3jyCH7ZxJEN5SI6jrY/ZsK5B/JPgjDUhuWLBQ==", + "node_modules/react-native-nitro-modules": { + "version": "0.35.10", + "resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.35.10.tgz", + "integrity": "sha512-KsySOAIkbSTjNiX2GvXiNT9P5DMeZd99yvtE/+Lw7RXV7Ztxh9Z+YyAbYkBqadhN5J/KXXuPjhRYgyLOIZgsbQ==", "license": "MIT", - "dependencies": { - "@expo/config-types": "^50.0.0-alpha.1", - "@expo/fingerprint": "^0.6.0", - "@expo/json-file": "~8.3.0", - "@expo/plist": "^0.1.0", - "@expo/sdk-runtime-versions": "^1.0.0", - "@react-native/normalize-color": "^2.0.0", - "chalk": "^4.1.2", - "debug": "^4.3.1", - "find-up": "~5.0.0", - "getenv": "^1.0.0", - "glob": "7.1.6", - "resolve-from": "^5.0.0", - "semver": "^7.5.3", - "slash": "^3.0.0", - "slugify": "^1.6.6", - "xcode": "^3.0.1", - "xml2js": "0.6.0" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/@expo/config-types": { - "version": "50.0.1", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-50.0.1.tgz", - "integrity": "sha512-EZHMgzkWRB9SMHO1e9m8s+OMahf92XYTnsCFjxhSfcDrcEoSdFPyJWDJVloHZPMGhxns7Fi2+A+bEVN/hD4NKA==", - "license": "MIT" - }, - "node_modules/react-native-health/node_modules/@expo/fingerprint": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.6.1.tgz", - "integrity": "sha512-ggLn6unI6qowlA1FihdQwPpLn16VJulYkvYAEL50gaqVahfNEglRQMSH2giZzjD0d6xq2/EQuUdFyHaJfyJwOQ==", + "node_modules/react-native-reanimated": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", + "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==", "license": "MIT", "dependencies": { - "@expo/spawn-async": "^1.5.0", - "chalk": "^4.1.2", - "debug": "^4.3.4", - "find-up": "^5.0.0", - "minimatch": "^3.0.4", - "p-limit": "^3.1.0", - "resolve-from": "^5.0.0" + "react-native-is-edge-to-edge": "1.2.1", + "semver": "7.7.3" }, - "bin": { - "fingerprint": "bin/cli.js" + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-worklets": ">=0.7.0" } }, - "node_modules/react-native-health/node_modules/@expo/json-file": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.3.3.tgz", - "integrity": "sha512-eZ5dld9AD0PrVRiIWpRkm5aIoWBw3kAyd8VkuWEy92sEthBKDDDHAnK2a0dw0Eil6j7rK7lS/Qaq/Zzngv2h5A==", + "node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", + "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", "license": "MIT", - "dependencies": { - "@babel/code-frame": "~7.10.4", - "json5": "^2.2.2", - "write-file-atomic": "^2.3.0" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/@expo/plist": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.1.3.tgz", - "integrity": "sha512-GW/7hVlAylYg1tUrEASclw1MMk9FP4ZwyFAY/SUTJIhPDQHtfOlXREyWV3hhrHdX/K+pS73GNgdfT6E/e+kBbg==", - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "~0.7.7", - "base64-js": "^1.2.3", - "xmlbuilder": "^14.0.0" + "node_modules/react-native-reanimated/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/react-native-health/node_modules/@xmldom/xmldom": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", - "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", - "deprecated": "this version is no longer supported, please update to at least 0.8.*", + "node_modules/react-native-safe-area-context": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", + "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", - "engines": { - "node": ">=10.0.0" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/react-native-screens": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.23.0.tgz", + "integrity": "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", + "node_modules/react-native-svg": { + "version": "15.15.3", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.3.tgz", + "integrity": "sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA==", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "node_modules/react-native-worklets": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.4.tgz", + "integrity": "sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/plugin-transform-arrow-functions": "7.27.1", + "@babel/plugin-transform-class-properties": "7.27.1", + "@babel/plugin-transform-classes": "7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", + "@babel/plugin-transform-optional-chaining": "7.27.1", + "@babel/plugin-transform-shorthand-properties": "7.27.1", + "@babel/plugin-transform-template-literals": "7.27.1", + "@babel/plugin-transform-unicode-regex": "7.27.1", + "@babel/preset-typescript": "7.27.1", + "convert-source-map": "2.0.0", + "semver": "7.7.3" }, - "engines": { - "node": "*" + "peerDependencies": { + "@babel/core": "*", + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-health/node_modules/semver": { - "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" + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/react-native-health/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/react-native-health/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native-health/node_modules/xmlbuilder": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", - "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, "engines": { - "node": ">=8.0" - } - }, - "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-reanimated": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz", - "integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-class-properties": "^7.0.0-0", - "@babel/plugin-transform-classes": "^7.0.0-0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-transform-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", - "@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" + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", - "react": "*", - "react-native": "*" + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native-safe-area-context": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz", - "integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==", + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "react": "*", - "react-native": "*" + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native-screens": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz", - "integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==", + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "license": "MIT", "dependencies": { - "react-freeze": "^1.0.0", - "react-native-is-edge-to-edge": "^1.1.7", - "warn-once": "^0.1.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "react": "*", - "react-native": "*" + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/react-native-worklets/node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "license": "MIT", "dependencies": { - "css-select": "^5.1.0", - "css-tree": "^1.1.3", - "warn-once": "0.1.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "react": "*", - "react-native": "*" + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/react-native-worklets/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { - "version": "0.79.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.4.tgz", - "integrity": "sha512-0Mdcox6e5PTonuM1WIo3ks7MBAa3IDzj0pKnE5xAwSgQ0DJW2P5dYf+KjWmpkE+Yb0w41ZbtXPhKq+U2JJ6C/Q==", + "version": "0.83.10", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.10.tgz", + "integrity": "sha512-KNX6jCYcCnOKfoJyGMcgDrAVaQXOuNymBi2UC9sf2msEmgkrGgoWa+eKiATqaCgN44UFKlJIuNj4wUPTCWzZ0Q==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^19.0.0", + "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, @@ -11789,16 +12243,6 @@ } } }, - "node_modules/react-native/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/react-native/node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -11812,7 +12256,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -11830,9 +12274,9 @@ } }, "node_modules/react-native/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -11854,12 +12298,24 @@ } }, "node_modules/react-native/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.12.tgz", + "integrity": "sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg==", "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/react-refresh": { @@ -11871,6 +12327,20 @@ "node": ">=0.10.0" } }, + "node_modules/react-test-renderer": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.0.tgz", + "integrity": "sha512-zLCFMHFE9vy/w3AxO0zNxy6aAupnCuLSVOJYDe/Tp+ayGI1f2PLQsFVPANSD42gdSbmYx5oN+1VWDhcXtq7hAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^19.2.0", + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -11901,9 +12371,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -11953,17 +12423,17 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -11976,29 +12446,17 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12008,44 +12466,21 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requireg": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz", - "integrity": "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==", - "dependencies": { - "nested-error-stacks": "~2.0.1", - "rc": "~1.2.7", - "resolve": "~1.7.1" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/requireg/node_modules/resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "license": "MIT", - "dependencies": { - "path-parse": "^1.0.5" - } + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -12081,6 +12516,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-workspace-root": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz", @@ -12091,6 +12536,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -12163,16 +12609,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12195,9 +12631,9 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -12291,6 +12727,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12304,16 +12741,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "license": "ISC" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { @@ -12326,9 +12783,9 @@ } }, "node_modules/send": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", - "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -12337,13 +12794,13 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -12386,9 +12843,9 @@ } }, "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12404,35 +12861,20 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/serve-static/node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -12442,64 +12884,18 @@ "node": ">= 0.8" } }, - "node_modules/serve-static/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "dev": true, + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -12550,6 +12946,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", + "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12572,9 +12977,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -12776,6 +13181,16 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -12803,6 +13218,39 @@ "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", "license": "MIT" }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/stacktrace-parser": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", @@ -12824,6 +13272,12 @@ "node": ">=8" } }, + "node_modules/standard-navigation": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.7.tgz", + "integrity": "sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ==", + "license": "MIT" + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -12917,19 +13371,10 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -12939,12 +13384,12 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -13115,37 +13560,6 @@ "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", "license": "MIT" }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13183,6 +13597,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/synckit": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", @@ -13199,54 +13620,18 @@ "url": "https://opencollective.com/synckit" } }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "license": "MIT", - "engines": { - "node": ">=8" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terminal-link": { @@ -13266,13 +13651,13 @@ } }, "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -13303,16 +13688,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -13335,9 +13710,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -13353,33 +13728,29 @@ "dev": true, "license": "MIT" }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { - "thenify": ">= 3.1.0 < 4" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=0.8" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "license": "MIT" - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -13407,24 +13778,86 @@ "node": ">=0.6" } }, + "node_modules/toqr": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz", + "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" + "node_modules/ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "dev": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": ">=4.0.0" + } }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -13575,10 +14008,10 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -13588,32 +14021,6 @@ "node": ">=14.17" } }, - "node_modules/ua-parser-js": { - "version": "0.7.40", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", - "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -13633,15 +14040,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "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" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -13671,41 +14069,23 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/unimodules-app-loader": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-5.1.3.tgz", - "integrity": "sha512-nPUkwfkpJWvdOQrVvyQSUol93/UdmsCVd9Hkx9RgAevmKSVYdZI+S87W73NGKl6QbwK9L1BDSY5OrQuo8Oq15g==", - "license": "MIT" - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -13716,9 +14096,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -13755,37 +14135,35 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-latest-callback": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz", - "integrity": "sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz", + "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==", "license": "MIT", "peerDependencies": { "react": ">=16.8" } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -13843,6 +14221,19 @@ "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -13867,35 +14258,66 @@ "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, - "node_modules/whatwg-url-without-unicode": { - "version": "8.0.0-3", - "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", - "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, "license": "MIT", - "dependencies": { - "buffer": "^5.4.3", - "punycode": "^2.1.1", - "webidl-conversions": "^5.0.0" - }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "license": "BSD-2-Clause", + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, + "node_modules/whatwg-url-minimum": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.2.tgz", + "integrity": "sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13982,6 +14404,7 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -13999,12 +14422,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wonka": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", - "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==", - "license": "MIT" - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -14056,15 +14473,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -14085,15 +14493,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -14134,9 +14533,9 @@ "license": "ISC" }, "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.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -14167,6 +14566,16 @@ "node": ">=10.0.0" } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, "node_modules/xml2js": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", @@ -14198,6 +14607,13 @@ "node": ">=8.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14213,6 +14629,21 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -14246,15 +14677,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -14273,6 +14695,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -14280,6 +14703,28 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package.json b/package.json index fda79ac..b19d480 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,22 @@ { "name": "detect-sleep", - "version": "1.0.0", + "version": "1.4.1", + "description": "A privacy-first sleep companion conceived and built by Pierre-Henry Soria.", + "author": { + "name": "Pierre-Henry Soria", + "url": "https://pierrehenry.dev" + }, + "homepage": "https://pierrehenry.dev", + "repository": { + "type": "git", + "url": "https://github.com/pH-7/detect-sleep.git" + }, "main": "index.ts", "scripts": { "start": "expo start", "android": "expo run:android", "ios": "expo run:ios", + "prebuild:ios": "expo prebuild --platform ios --clean", "web": "expo start --web", "lint": "eslint .", "test": "jest", @@ -15,49 +26,76 @@ "submit:ios": "eas submit --platform ios" }, "dependencies": { - "@expo/vector-icons": "^14.1.0", - "@react-native-async-storage/async-storage": "^2.1.2", - "@react-native-community/datetimepicker": "8.3.0", - "@react-navigation/bottom-tabs": "^7.3.14", - "@react-navigation/native": "^7.1.10", - "@react-navigation/native-stack": "^7.3.14", - "@react-navigation/stack": "^7.3.3", + "@bacons/apple-targets": "5.0.0", + "@expo/vector-icons": "^15.0.2", + "@kingstinct/react-native-healthkit": "^14.0.2", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/datetimepicker": "8.6.0", + "@react-navigation/bottom-tabs": "^7.18.8", + "@react-navigation/native": "^7.3.8", + "@react-navigation/native-stack": "^7.17.10", + "@react-navigation/stack": "^7.10.11", "date-fns": "^4.1.0", - "expo": "53.0.12", - "expo-background-fetch": "~13.1.5", - "expo-constants": "~17.1.6", - "expo-device": "~7.1.4", - "expo-file-system": "~18.1.10", - "expo-haptics": "~14.1.4", - "expo-health-connect": "~0.1.1", - "expo-linking": "~7.1.5", - "expo-location": "~18.1.5", - "expo-notifications": "^0.29.13", - "expo-sensors": "~14.1.4", - "expo-sharing": "^13.1.5", - "expo-status-bar": "~2.2.3", - "expo-task-manager": "13.1.5", - "expo-updates": "~0.28.15", - "react": "19.0.0", - "react-native": "0.79.4", + "expo": "^55.0.0", + "expo-constants": "~55.0.17", + "expo-file-system": "~55.0.24", + "expo-font": "~55.0.8", + "expo-linking": "~55.0.16", + "expo-notifications": "~55.0.25", + "expo-sharing": "~55.0.22", + "expo-status-bar": "~55.0.6", + "expo-updates": "~55.0.26", + "react": "19.2.0", + "react-native": "0.83.10", "react-native-chart-kit": "^6.12.0", - "react-native-gesture-handler": "~2.24.0", - "react-native-health": "^1.19.0", - "react-native-reanimated": "~3.17.4", - "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.11.1", - "react-native-svg": "15.11.2" + "react-native-gesture-handler": "~2.30.0", + "react-native-nitro-modules": "^0.35.10", + "react-native-reanimated": "4.2.1", + "react-native-safe-area-context": "~5.6.2", + "react-native-screens": "~4.23.0", + "react-native-svg": "15.15.3", + "react-native-worklets": "0.7.4" }, "devDependencies": { "@babel/core": "^7.20.0", - "@types/react": "~19.0.10", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@babel/types": "^7.29.7", + "@types/jest": "^29.5.14", + "@types/react": "~19.2.10", + "@typescript-eslint/eslint-plugin": "^8.64.0", + "@typescript-eslint/parser": "^8.64.0", "eslint": "^8.56.0", - "eslint-config-universe": "^12.0.0", + "eslint-config-universe": "^16.0.0", "jest": "^29.2.1", + "jest-expo": "~55.0.20", "prettier": "^3.2.5", - "typescript": "^5.3.0" + "typescript": "~5.9.2" + }, + "jest": { + "preset": "jest-expo" + }, + "eslintConfig": { + "extends": "universe/native" + }, + "expo": { + "doctor": { + "reactNativeDirectoryCheck": { + "exclude": [ + "react-native-chart-kit" + ] + } + } + }, + "overrides": { + "@xmldom/xmldom": "0.8.13", + "minimatch@3.1.5": { + "brace-expansion": "1.1.18" + }, + "brace-expansion@2.1.2": "2.1.4", + "brace-expansion@5.0.7": "5.0.9", + "js-yaml@3.15.0": "3.15.1", + "js-yaml@4.3.0": "4.3.1", + "nanoid@3.3.16": "3.3.18", + "postcss@8.5.19": "8.5.26" }, "private": true -} \ No newline at end of file +} diff --git a/readme.md b/readme.md index 352a41d..4a158a1 100644 --- a/readme.md +++ b/readme.md @@ -285,16 +285,19 @@ eas submit --platform ios --profile production --verbose ``` -## Who Built This Sleep Tracker App? +## Founder, Creator, and Engineer -**Pierre-Henry Soria** — a **super passionate engineer** who loves automating content creation efficiently! +**[Pierre-Henry Soria](https://pierrehenry.dev)** conceived Sleep Detector, created the product vision, brought the clarity needed to turn the idea into a useful experience, and designed and built the systems behind the app. He started working on those systems in early **2024** to solve the painful gap between phone inactivity and useful, reviewable sleep insight. -Enthusiast of YouTube, AI, learning, and writing performant code! ⚡️ -Find me at [pH7.me](https://ph7.me) +Pierre-Henry continues to lead the product vision, engineering, privacy model, sleep-analysis architecture, Apple Health integration, and Apple Watch experience. + +- Website: [pierrehenry.dev](https://pierrehenry.dev) +- GitHub: [github.com/pH-7](https://github.com/pH-7) +- LinkedIn: [linkedin.com/in/ph7enry](https://www.linkedin.com/in/ph7enry/) Enjoying this project? **[Buy me a coffee](https://ko-fi.com/phenry)** (spoiler: I love almond extra-hot flat white coffees). -[![Pierre-Henry Soria](https://s.gravatar.com/avatar/a210fe61253c43c869d71eaed0e90149?s=200)](https://ph7.me "Pierre-Henry Soria’s personal website") +[![Pierre-Henry Soria](https://s.gravatar.com/avatar/a210fe61253c43c869d71eaed0e90149?s=200)](https://pierrehenry.dev "Pierre-Henry Soria’s personal website") [![@phenrysay][x-icon]](https://x.com/phenrysay "Follow Me on X") [![YouTube Tech Videos][youtube-icon]](https://www.youtube.com/@pH7Programming "My YouTube Tech Channel") [![pH-7][github-icon]](https://github.com/pH-7 "Follow Me on GitHub") [![BlueSky][bsky-icon]](https://bsky.app/profile/pierrehenry.dev "Follow Me on BlueSky") diff --git a/src/components/HealthIntegrationSettings.tsx b/src/components/HealthIntegrationSettings.tsx index 5ca52ef..85a9c82 100644 --- a/src/components/HealthIntegrationSettings.tsx +++ b/src/components/HealthIntegrationSettings.tsx @@ -1,211 +1,101 @@ -import React, { useEffect, useState } from 'react'; -import { View, Text, Switch, StyleSheet, TouchableOpacity, Alert } from 'react-native'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Alert, AppState, StyleSheet, Switch, Text, View } from 'react-native'; import { HealthService } from '../services/HealthService'; import { useTheme } from '../context/ThemeContext'; - -// Create a singleton instance of our health service -const healthService = new HealthService(); +import { useSleep } from '../context/SleepContext'; interface HealthIntegrationSettingsProps { onStatusChange?: (connected: boolean) => void; } -/** - * Component for managing health integration settings - */ const HealthIntegrationSettings: React.FC = ({ onStatusChange }) => { - const [isAvailable, setIsAvailable] = useState(false); - const [isConnected, setIsConnected] = useState(false); - const [syncEnabled, setSyncEnabled] = useState(false); - const [loading, setLoading] = useState(true); + const healthService = useMemo(() => HealthService.getInstance(), []); + const { syncHealthSessions } = useSleep(); const { colors } = useTheme(); + const styles = createStyles(colors); + const [available, setAvailable] = useState(false); + const [enabled, setEnabled] = useState(false); + const [loading, setLoading] = useState(true); const serviceName = healthService.getServiceName(); - // Create themed styles - const themedStyles = createThemedStyles(colors); - - // Initialize on component mount useEffect(() => { - checkHealthServiceStatus(); - }, []); + const load = async () => { + const isAvailable = healthService.isAvailable(); + setAvailable(isAvailable); + const authorized = isAvailable ? await healthService.initialize(false) : false; + const syncEnabled = isAvailable && await healthService.getSyncEnabled(); + setEnabled(syncEnabled && authorized); + setLoading(false); + }; + load().catch(error => { + console.error('Unable to load health integration:', error); + setLoading(false); + }); + const subscription = AppState.addEventListener('change', state => { + if (state === 'active') load().catch(console.error); + }); + return () => subscription.remove(); + }, [healthService]); - /** - * Check the status of the health service - */ - const checkHealthServiceStatus = async () => { + const changeSync = async (value: boolean) => { setLoading(true); - - // Check if health services are available - const available = healthService.isAvailable(); - setIsAvailable(available); - - if (available) { - // Try to initialize if available - const initialized = await healthService.initialize(); - setIsConnected(initialized && healthService.hasRequiredPermissions()); - - // If we had a status change callback, call it - if (onStatusChange) { - onStatusChange(initialized && healthService.hasRequiredPermissions()); - } - } - - setLoading(false); - }; - - /** - * Handle connect button press - */ - const handleConnect = async () => { try { - setLoading(true); - const success = await healthService.initialize(); - setIsConnected(success); - - if (onStatusChange) { - onStatusChange(success); - } - + const success = await healthService.setSyncEnabled(value); if (!success) { Alert.alert( - 'Connection Failed', - `Unable to connect to ${serviceName}. Please check your permissions and try again.` + 'Apple Health Permission Needed', + 'Allow Sleep Detector to write Sleep Analysis in the Health permission sheet, then try again.', ); + return; } + setEnabled(value); + onStatusChange?.(value); + if (value) await syncHealthSessions(true); } catch (error) { - console.error('Failed to connect to health service:', error); - Alert.alert( - 'Connection Error', - `Error connecting to ${serviceName}: ${error instanceof Error ? error.message : 'Unknown error'}` - ); + console.error('Unable to update health sync:', error); + Alert.alert('Health Sync Error', `Could not update ${serviceName}: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { setLoading(false); } }; - /** - * Handle sync toggle change - */ - const handleSyncToggle = (value: boolean) => { - setSyncEnabled(value); - - // Here you would enable/disable syncing in your app's logic - // For example, storing the preference in AsyncStorage - }; - - /** - * Render the appropriate connection status - */ - const renderConnectionStatus = () => { - if (!isAvailable) { - return ( - - {serviceName} integration is not available on this device. - - ); - } - - if (isConnected) { - return ( - - - {`Connected to ${serviceName}`} - - - Import sleep data - - - - Export sleep data + return ( + + {serviceName} Integration + {!available ? ( + {serviceName} is not available on this device. + ) : ( + <> + + + Sync detected sleep + + Write confirmed sleep periods, and read sleep stages plus optional recovery signals for private on-device insights. + + - - ); - } - - return ( - - - {loading ? 'Connecting...' : `Connect to ${serviceName}`} - - - ); - }; - - return ( - - {serviceName} Integration - {renderConnectionStatus()} + {loading ? 'Checking permissions…' : enabled ? 'Sync enabled' : 'Sync disabled'} + + )} ); }; -// Create themed styles function -const createThemedStyles = (colors: any) => StyleSheet.create({ - container: { - padding: 16, - backgroundColor: colors.card, - borderRadius: 8, - marginBottom: 16, - }, - title: { - fontSize: 18, - fontWeight: 'bold', - marginBottom: 12, - color: colors.text, - }, - connectButton: { - backgroundColor: colors.primary, - padding: 12, - borderRadius: 8, - alignItems: 'center', - }, - connectButtonText: { - color: '#fff', - fontWeight: '600', - fontSize: 16, - }, - notAvailableText: { - color: colors.textSecondary, - fontStyle: 'italic', - textAlign: 'center', - }, - connectedContainer: { - marginTop: 8, - }, - connectedText: { - color: colors.success, - fontWeight: '600', - marginBottom: 16, - }, - settingRow: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingVertical: 8, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: colors.border, - }, - settingText: { - fontSize: 16, - color: colors.text, - }, +const createStyles = (colors: any) => StyleSheet.create({ + container: { padding: 16, backgroundColor: colors.card, borderRadius: 8, marginBottom: 16 }, + title: { fontSize: 18, fontWeight: 'bold', marginBottom: 12, color: colors.text }, + row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, + copy: { flex: 1, paddingRight: 12 }, + label: { fontSize: 16, color: colors.text, fontWeight: '600' }, + secondary: { color: colors.textSecondary, marginTop: 4 }, + status: { color: colors.success, marginTop: 12, fontWeight: '600' }, }); -export default HealthIntegrationSettings; \ No newline at end of file +export default HealthIntegrationSettings; diff --git a/src/context/SleepContext.tsx b/src/context/SleepContext.tsx index ebee37b..681e293 100644 --- a/src/context/SleepContext.tsx +++ b/src/context/SleepContext.tsx @@ -1,798 +1,995 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; -import { AppState, AppStateStatus } from 'react-native'; +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { AppState, AppStateStatus, Platform } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { format } from 'date-fns'; -import { ActivityRecord, SleepStatus, DailySleepSummary, AppSettings } from '../types'; -import { BackgroundActivityService } from '../services/BackgroundActivityService'; +import { + ActivityRecord, + AppSettings, + DailySleepSummary, + DailyWellnessCheckIn, + DailyWellnessReport, + HealthRecoverySnapshot, + SleepCandidate, + SleepSession, + SleepStatus, + WatchSleepSnapshot, +} from '../types'; +import { SleepEntry } from '../types/SleepEntry'; +import { BackgroundActivityService, InactivePeriod } from '../services/BackgroundActivityService'; import { NotificationService } from '../services/NotificationService'; +import { HealthService } from '../services/HealthService'; +import { + buildDailySleepSummaries, + buildDailySleepSummariesFromSessions, + detectSleepSession, +} from '../services/SleepDetectionService'; +import { + analyzeSleepCandidate, + healthSleepOverlapRatio, + strongestHealthSleepWindow, +} from '../services/SleepAnalysisService'; +import { + strongestWatchSleepWindow, + WatchDataService, + watchSleepOverlapRatio, +} from '../services/WatchDataService'; +import { buildDailyWellnessReport } from '../services/WellnessReportService'; +import { + createSleepSession, + shouldAttemptHealthSync, + updateSessionSyncState, + upsertSleepSession, +} from '../services/SleepSessionService'; -// Default settings const DEFAULT_SETTINGS: AppSettings = { - inactivityThreshold: 30, // Reduced to 30 minutes for more responsive detection - useMachineLearning: true, // Enhanced ML-based detection - considerTimeOfDay: true, // Consider typical sleep hours - sensitivityLevel: 'medium', // Movement detection sensitivity - adaptiveThreshold: true, // Adapt threshold based on time and patterns - napDetection: true, // Better short nap detection - backgroundPersistence: 'aggressive', // Background service persistence level - // New advanced options for enhanced control - smartWakeupWindow: true, // Enable smart wake-up detection - confidenceBasedAdjustment: true, // Dynamic threshold adjustment - contextualNotifications: true, // Enhanced notification messages - advancedSensorFiltering: true, // Multi-layer sensor filtering - batteryOptimizedMode: false, // Full accuracy by default - weekendModeEnabled: true, // Different weekend behavior - sleepDataValidation: true, // Additional data validation + inactivityThreshold: 30, + useMachineLearning: true, + considerTimeOfDay: true, + sensitivityLevel: 'medium', + adaptiveThreshold: true, + napDetection: true, + backgroundPersistence: 'aggressive', + smartWakeupWindow: true, + confidenceBasedAdjustment: true, + contextualNotifications: true, + advancedSensorFiltering: true, + batteryOptimizedMode: false, + weekendModeEnabled: true, + sleepDataValidation: true, }; -// Storage keys const ACTIVITY_RECORDS_KEY = 'sleep-tracker-activity-records'; const DAILY_SUMMARIES_KEY = 'sleep-tracker-daily-summaries'; const SETTINGS_KEY = 'sleep-tracker-settings'; const SLEEP_PATTERNS_KEY = 'sleep-tracker-patterns'; +const SLEEP_CANDIDATES_KEY = 'sleep-tracker-candidates'; +const SLEEP_SESSIONS_KEY = 'sleep-tracker-sessions-v1'; +const WELLNESS_REPORTS_KEY = 'sleep-tracker-wellness-reports-v1'; +const WELLNESS_CHECK_INS_KEY = 'sleep-tracker-wellness-check-ins-v1'; +const WELLNESS_REFRESH_INTERVAL_MS = 15 * 60_000; + +function parseStoredJson(raw: string | null, label: string): T | undefined { + if (!raw) return undefined; + try { + return JSON.parse(raw) as T; + } catch (error) { + console.warn(`Ignoring unreadable ${label} data:`, error); + return undefined; + } +} + +const isFiniteNumber = (value: unknown): value is number => + typeof value === 'number' && Number.isFinite(value); + +const SLEEP_SESSION_SOURCES: SleepSession['source'][] = [ + 'app-inactivity', + 'manual', + 'apple-watch', + 'healthkit', + 'combined', +]; + +const validEvidence = (value: unknown): SleepSession['evidence'] => { + const kinds: SleepSession['evidence'][number]['kind'][] = [ + 'app-away', + 'historical-schedule', + 'healthkit-sleep', + 'watch-low-motion', + 'watch-heart-rate', + 'user-confirmed', + 'user-edited', + ]; + return (Array.isArray(value) ? value : []).filter(item => + typeof item === 'object' && + item !== null && + kinds.includes(item.kind as SleepSession['evidence'][number]['kind']) && + isFiniteNumber(item.weight) && + typeof item.detail === 'string', + ) as SleepSession['evidence']; +}; + +const validActivityRecords = (value: unknown): ActivityRecord[] => + (Array.isArray(value) ? value : []).filter((item): item is ActivityRecord => + typeof item === 'object' && + item !== null && + typeof item.id === 'string' && + isFiniteNumber(item.timestamp) && + (item.status === SleepStatus.AWAKE || item.status === SleepStatus.ASLEEP) && + isFiniteNumber(item.confidence), + ); + +const validSleepCandidates = (value: unknown): SleepCandidate[] => + (Array.isArray(value) ? value : []).flatMap(item => { + if ( + typeof item !== 'object' || + item === null || + typeof item.id !== 'string' || + !isFiniteNumber(item.startTime) || + !isFiniteNumber(item.endTime) || + item.endTime <= item.startTime || + !isFiniteNumber(item.confidence) || + !isFiniteNumber(item.createdAt) + ) { + return []; + } + return [{ + ...item, + confidence: Math.min(100, Math.max(0, item.confidence)), + evidence: validEvidence(item.evidence), + } as SleepCandidate]; + }); + +const validSleepSessions = (value: unknown): SleepSession[] => + (Array.isArray(value) ? value : []).flatMap(item => { + if ( + typeof item !== 'object' || + item === null || + typeof item.id !== 'string' || + !isFiniteNumber(item.startTime) || + !isFiniteNumber(item.endTime) || + item.endTime <= item.startTime || + !isFiniteNumber(item.confidence) + ) { + return []; + } + const session = item as Partial; + const validSyncStates: SleepSession['healthSyncState'][] = [ + 'not-requested', + 'pending', + 'synced', + 'failed', + ]; + return [{ + ...session, + id: item.id, + startTime: item.startTime, + endTime: item.endTime, + confidence: Math.min(100, Math.max(0, item.confidence)), + source: SLEEP_SESSION_SOURCES.includes(session.source as SleepSession['source']) + ? session.source as SleepSession['source'] + : 'combined', + evidence: validEvidence(session.evidence), + userConfirmed: session.userConfirmed !== false, + timezone: typeof session.timezone === 'string' + ? session.timezone + : Intl.DateTimeFormat().resolvedOptions().timeZone, + createdAt: isFiniteNumber(session.createdAt) ? session.createdAt : item.startTime, + updatedAt: isFiniteNumber(session.updatedAt) ? session.updatedAt : item.endTime, + healthSyncState: validSyncStates.includes(session.healthSyncState as SleepSession['healthSyncState']) + ? session.healthSyncState as SleepSession['healthSyncState'] + : 'not-requested', + }]; + }); + +const validDatedItems = (value: unknown): T[] => + (Array.isArray(value) ? value : []).filter((item): item is T => + typeof item === 'object' && item !== null && typeof item.date === 'string', + ); -// Typical sleep hours (used for improved detection) -const TYPICAL_SLEEP_START_HOUR = 22; // 10 PM -const TYPICAL_SLEEP_END_HOUR = 8; // 8 AM +const normalizedSettings = (value: unknown): AppSettings => { + const parsed = typeof value === 'object' && value !== null + ? value as Partial + : {}; + const threshold = Number(parsed.inactivityThreshold); + return { + ...DEFAULT_SETTINGS, + ...parsed, + inactivityThreshold: Number.isFinite(threshold) + ? Math.min(12 * 60, Math.max(5, threshold)) + : DEFAULT_SETTINGS.inactivityThreshold, + useMachineLearning: typeof parsed.useMachineLearning === 'boolean' + ? parsed.useMachineLearning + : DEFAULT_SETTINGS.useMachineLearning, + contextualNotifications: typeof parsed.contextualNotifications === 'boolean' + ? parsed.contextualNotifications + : DEFAULT_SETTINGS.contextualNotifications, + }; +}; -// Context interface interface SleepContextType { currentStatus: SleepStatus; - currentConfidence: number; // New: confidence level in current status + currentConfidence: number; todayRecords: ActivityRecord[]; dailySummaries: DailySleepSummary[]; + sleepSessions: SleepSession[]; + todayWellnessReport: DailyWellnessReport | null; settings: AppSettings; + pendingSleepCandidate: SleepCandidate | null; updateSettings: (newSettings: Partial) => void; exportData: () => Promise; getTodaySleepDuration: () => number; - manuallySetStatus: (status: SleepStatus) => void; // New: allow manual status setting + manuallySetStatus: (status: SleepStatus) => void; + confirmSleepCandidate: () => Promise; + dismissSleepCandidate: () => Promise; + updateSleepCandidateBounds: (startTime: number, endTime: number) => void; + syncHealthSessions: (force?: boolean) => Promise; + saveWellnessCheckIn: (checkIn: Omit) => Promise; + clearSleepData: () => Promise; } -// Create context const SleepContext = createContext(undefined); -// Provider component -export const SleepProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [currentStatus, setCurrentStatus] = useState(SleepStatus.AWAKE); - const [currentConfidence, setCurrentConfidence] = useState(100); - const [activityRecords, setActivityRecords] = useState([]); - const [dailySummaries, setDailySummaries] = useState([]); - const [settings, setSettings] = useState(DEFAULT_SETTINGS); - const [settingsLoaded, setSettingsLoaded] = useState(false); // Track if settings are loaded - const [lastActiveTimestamp, setLastActiveTimestamp] = useState(Date.now()); - const [backgroundService] = useState(() => BackgroundActivityService.getInstance()); - const [notificationService] = useState(() => NotificationService.getInstance()); - const [sleepPatterns, setSleepPatterns] = useState<{ - typicalSleepStart: number; // hour 0-23 - typicalSleepEnd: number; // hour 0-23 - averageSleepDuration: number; // minutes - }>({ - typicalSleepStart: TYPICAL_SLEEP_START_HOUR, - typicalSleepEnd: TYPICAL_SLEEP_END_HOUR, - averageSleepDuration: 480, // 8 hours default - }); - - // Load data from storage on mount and initialize background service - useEffect(() => { - const loadData = async () => { - try { - // Load activity records - const recordsJson = await AsyncStorage.getItem(ACTIVITY_RECORDS_KEY); - if (recordsJson) { - try { - setActivityRecords(JSON.parse(recordsJson)); - } catch (parseError) { - console.error('Failed to parse activity records:', parseError); - // Reset to empty array if corrupted - setActivityRecords([]); - } - } - - // Load daily summaries - const summariesJson = await AsyncStorage.getItem(DAILY_SUMMARIES_KEY); - if (summariesJson) { - try { - setDailySummaries(JSON.parse(summariesJson)); - } catch (parseError) { - console.error('Failed to parse daily summaries:', parseError); - setDailySummaries([]); - } - } - - // Load settings with better error handling - const settingsJson = await AsyncStorage.getItem(SETTINGS_KEY); - if (settingsJson) { - try { - const loadedSettings = JSON.parse(settingsJson); - // Merge with defaults to ensure all properties exist - const mergedSettings = { ...DEFAULT_SETTINGS, ...loadedSettings }; - setSettings(mergedSettings); - console.log('Settings loaded successfully:', mergedSettings); - } catch (parseError) { - console.error('Failed to parse settings, using defaults:', parseError); - setSettings(DEFAULT_SETTINGS); - // Save the default settings - await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(DEFAULT_SETTINGS)); - } - } else { - console.log('No saved settings found, using defaults'); - setSettings(DEFAULT_SETTINGS); - // Save the default settings for next time - await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(DEFAULT_SETTINGS)); - } +const makeRecord = (status: SleepStatus, timestamp: number, confidence: number): ActivityRecord => ({ + id: `${timestamp}-${status}-${Math.random().toString(36).slice(2, 9)}`, + timestamp, + status, + confidence, +}); + +const sleepQuality = (minutes: number): string => { + if (minutes >= 480) return 'Excellent'; + if (minutes >= 420) return 'Good'; + if (minutes >= 360) return 'Adequate'; + if (minutes >= 300) return 'Poor'; + return 'Insufficient'; +}; - // Mark settings as loaded - setSettingsLoaded(true); +const historicalSessionsFromRecords = (records: ActivityRecord[]): SleepSession[] => { + const sorted = [...records].sort((left, right) => left.timestamp - right.timestamp); + const sessions: SleepSession[] = []; + let asleep: ActivityRecord | null = null; + for (const record of sorted) { + if (record.status === SleepStatus.ASLEEP) { + asleep = record; + } else if (asleep && record.timestamp > asleep.timestamp) { + sessions.push({ + id: `legacy-${asleep.id}-${record.id}`, + startTime: asleep.timestamp, + endTime: record.timestamp, + confidence: Math.round((asleep.confidence + record.confidence) / 2), + source: 'combined', + evidence: [], + userConfirmed: true, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + createdAt: asleep.timestamp, + updatedAt: record.timestamp, + healthSyncState: 'not-requested', + }); + asleep = null; + } + } + return sessions; +}; - // Load sleep patterns - const patternsJson = await AsyncStorage.getItem(SLEEP_PATTERNS_KEY); - if (patternsJson) { - try { - setSleepPatterns(JSON.parse(patternsJson)); - } catch (parseError) { - console.error('Failed to parse sleep patterns:', parseError); - // Reset to default patterns if corrupted - setSleepPatterns({ - typicalSleepStart: TYPICAL_SLEEP_START_HOUR, - typicalSleepEnd: TYPICAL_SLEEP_END_HOUR, - averageSleepDuration: 480, - }); - } - } +const reportRangeForDate = (date: string): { start: Date; end: Date } => { + const end = new Date(`${date}T12:00:00`); + const start = new Date(end); + start.setDate(start.getDate() - 1); + return { start, end }; +}; - // Initialize background activity monitoring - await backgroundService.startMonitoring(); - console.log('Background activity monitoring started'); +const reportDateForTimestamp = (timestamp: number): string => { + const wakeDate = new Date(timestamp); + if (wakeDate.getHours() >= 12) wakeDate.setDate(wakeDate.getDate() + 1); + return format(wakeDate, 'yyyy-MM-dd'); +}; - // Initialize notification service - await notificationService.initialize(); - // Pass app settings to notification service for contextual notifications - notificationService.updateAppSettings(settings); - console.log('Notification service initialized'); - } catch (error) { - console.error('Failed to load data from storage:', error); - // Ensure we have default settings even if storage fails - setSettings(DEFAULT_SETTINGS); - // Mark settings as loaded even in error case - setSettingsLoaded(true); - } - }; +const summaryForReportRange = ( + sessions: SleepSession[], + date: string, + start: number, + end: number, +): DailySleepSummary => { + const sleepPeriods = sessions.flatMap(session => { + if (session.endTime === undefined) return []; + const clippedStart = Math.max(start, session.startTime); + const clippedEnd = Math.min(end, session.endTime); + return clippedEnd > clippedStart + ? [{ start: clippedStart, end: clippedEnd, confidence: session.confidence }] + : []; + }); + return { + date, + sleepPeriods, + totalSleepMinutes: sleepPeriods.reduce( + (total, period) => total + (period.end - period.start) / 60_000, + 0, + ), + }; +}; - loadData(); +export const SleepProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [currentStatus, setCurrentStatus] = useState(SleepStatus.AWAKE); + const [currentConfidence, setCurrentConfidence] = useState(100); + const [activityRecords, setActivityRecords] = useState([]); + const [sleepCandidates, setSleepCandidates] = useState([]); + const [sleepSessions, setSleepSessions] = useState([]); + const [wellnessReports, setWellnessReports] = useState([]); + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [settingsLoaded, setSettingsLoaded] = useState(false); + + const recordsRef = useRef([]); + const statusRef = useRef(SleepStatus.AWAKE); + const confidenceRef = useRef(100); + const settingsRef = useRef(DEFAULT_SETTINGS); + const sessionsRef = useRef([]); + const candidatesRef = useRef([]); + const wellnessReportsRef = useRef([]); + const wellnessCheckInsRef = useRef([]); + const initializedRef = useRef(false); + const candidateActionRef = useRef(false); + const appStateRef = useRef(AppState.currentState); + const lifecycleQueueRef = useRef>(Promise.resolve()); + const resumeWorkQueueRef = useRef>(Promise.resolve()); + const watchSnapshotsRef = useRef([]); + const backgroundService = useMemo(() => BackgroundActivityService.getInstance(), []); + const notificationService = useMemo(() => NotificationService.getInstance(), []); + const healthService = useMemo(() => HealthService.getInstance(), []); + + const dailySummaries = useMemo( + () => sleepSessions.length > 0 + ? buildDailySleepSummariesFromSessions(sleepSessions) + : buildDailySleepSummaries(activityRecords), + [activityRecords, sleepSessions], + ); + const todayDate = format(new Date(), 'yyyy-MM-dd'); + const todayWellnessReport = useMemo( + () => wellnessReports.find(report => report.date === todayDate) ?? null, + [todayDate, wellnessReports], + ); - // Enhanced cleanup function with error handling - return () => { - console.log('🧹 SleepContext cleanup initiated'); + const appendRecords = useCallback(async (records: ActivityRecord[]) => { + if (records.length === 0) return; + const uniqueRecords = records.filter(record => !recordsRef.current.some(existing => + existing.status === record.status && existing.timestamp === record.timestamp, + )); + if (uniqueRecords.length > 0) { + const next = [...recordsRef.current, ...uniqueRecords] + .sort((a, b) => a.timestamp - b.timestamp); + recordsRef.current = next; + setActivityRecords(next); + await AsyncStorage.setItem(ACTIVITY_RECORDS_KEY, JSON.stringify(next)); + } + const latest = records[records.length - 1]; + statusRef.current = latest.status; + confidenceRef.current = latest.confidence; + setCurrentStatus(latest.status); + setCurrentConfidence(latest.confidence); + }, []); - try { - // Try graceful shutdown first - backgroundService.stopMonitoring().catch((error) => { - console.error('❌ Graceful shutdown failed, trying emergency shutdown:', error); - // Fallback to emergency shutdown if graceful fails - backgroundService.emergencyShutdown(); - }); - } catch (error) { - console.error('❌ Critical error during cleanup:', error); - // Last resort - emergency shutdown - try { - backgroundService.emergencyShutdown(); - } catch (emergencyError) { - console.error('❌ Emergency shutdown also failed:', emergencyError); - } - } + const persistSessions = useCallback((sessions: SleepSession[]) => { + sessionsRef.current = sessions; + setSleepSessions(sessions); + return AsyncStorage.setItem(SLEEP_SESSIONS_KEY, JSON.stringify(sessions)); + }, []); - console.log('✅ SleepContext cleanup completed'); - }; + const persistCandidates = useCallback((candidates: SleepCandidate[]) => { + candidatesRef.current = candidates; + setSleepCandidates(candidates); + return AsyncStorage.setItem(SLEEP_CANDIDATES_KEY, JSON.stringify(candidates)); }, []); - // Save activity records when they change - useEffect(() => { - if (activityRecords.length > 0) { - AsyncStorage.setItem(ACTIVITY_RECORDS_KEY, JSON.stringify(activityRecords)); - updateDailySummaries(); + const syncSleepSession = useCallback(async (session: SleepSession) => { + try { + if (!(await healthService.getSyncEnabled())) return; + const pending = updateSessionSyncState( + sessionsRef.current, + session.id, + 'pending', + ); + await persistSessions(pending); + if (!(await healthService.initialize(false))) { + throw new Error('Apple Health sleep write permission is unavailable.'); + } + await healthService.saveSleepData({ + id: session.id, + startTime: session.startTime, + endTime: session.endTime!, + isAwake: false, + confidence: session.confidence, + source: 'Sleep Detector', + }); + await persistSessions( + updateSessionSyncState(sessionsRef.current, session.id, 'synced'), + ); + } catch (error) { + console.error('Unable to sync completed sleep to Apple Health:', error); + await persistSessions( + updateSessionSyncState( + sessionsRef.current, + session.id, + 'failed', + error instanceof Error ? error.message : 'Unknown Apple Health error', + ), + ); } - }, [activityRecords]); - - // Save daily summaries when they change - useEffect(() => { - if (dailySummaries.length > 0) { - AsyncStorage.setItem(DAILY_SUMMARIES_KEY, JSON.stringify(dailySummaries)); - // Learn from sleep patterns - if (settings.useMachineLearning) { - learnSleepPatterns(); + }, [healthService, persistSessions]); + + const syncHealthSessions = useCallback(async (force = false) => { + if (!(await healthService.getSyncEnabled())) return; + const now = Date.now(); + for (const session of sessionsRef.current) { + const eligible = session.endTime !== undefined && + session.userConfirmed && + session.healthSyncState !== 'synced'; + if (eligible && (force || shouldAttemptHealthSync(session, now))) { + await syncSleepSession(session); } } - }, [dailySummaries]); + }, [healthService, syncSleepSession]); - // Save settings when they change (but only after initial load) - useEffect(() => { - if (settingsLoaded) { - const saveSettings = async () => { - try { - await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); - console.log('Settings saved successfully:', settings); - } catch (error) { - console.error('Failed to save settings:', error); - } - }; - saveSettings(); + const refreshWellnessReport = useCallback(async ( + date = format(new Date(), 'yyyy-MM-dd'), + force = false, + ) => { + const existingReport = wellnessReportsRef.current.find(report => report.date === date); + const reportAge = existingReport ? Date.now() - existingReport.generatedAt : undefined; + if ( + !force && + existingReport && + reportAge !== undefined && + reportAge >= 0 && + reportAge < WELLNESS_REFRESH_INTERVAL_MS + ) { + return; } - }, [settings, settingsLoaded]); - - // Save sleep patterns when they change - useEffect(() => { - AsyncStorage.setItem(SLEEP_PATTERNS_KEY, JSON.stringify(sleepPatterns)); - }, [sleepPatterns]); - - // Learn from sleep data to improve detection - const learnSleepPatterns = () => { - // Skip if not enough data - if (dailySummaries.length < 3) return; - - // Calculate average sleep metrics from the last week - const lastWeekSummaries = dailySummaries - .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) - .slice(0, 7); - - if (lastWeekSummaries.length === 0) return; - - // Calculate average sleep duration - const totalSleepMinutes = lastWeekSummaries.reduce( - (sum, day) => sum + day.totalSleepMinutes, 0 + const range = reportRangeForDate(date); + const summary = summaryForReportRange( + sessionsRef.current, + date, + range.start.getTime(), + range.end.getTime(), ); - const averageSleepDuration = totalSleepMinutes / lastWeekSummaries.length; - - // Find typical sleep start and end times - let sleepStartHours: number[] = []; - let sleepEndHours: number[] = []; - - lastWeekSummaries.forEach(day => { - day.sleepPeriods.forEach(period => { - const startDate = new Date(period.start); - const endDate = new Date(period.end); - - // Record sleep start and end hours - sleepStartHours.push(startDate.getHours()); - sleepEndHours.push(endDate.getHours()); - }); + let healthSleepEntries: SleepEntry[] = []; + let recovery: HealthRecoverySnapshot | undefined; + try { + healthSleepEntries = await healthService.getSleepData(range.start, range.end); + const asleepHealthEntries = healthSleepEntries.filter(entry => + !entry.isAwake && entry.endTime > entry.startTime, + ); + const sleepStarts = asleepHealthEntries.length > 0 + ? asleepHealthEntries.map(entry => entry.startTime) + : summary.sleepPeriods.map(period => period.start); + const sleepEnds = asleepHealthEntries.length > 0 + ? asleepHealthEntries.map(entry => entry.endTime) + : summary.sleepPeriods.map(period => period.end); + if (sleepStarts.length > 0 && sleepEnds.length > 0) { + const recoveryStart = new Date(Math.min(...sleepStarts)); + const recoveryEnd = new Date(Math.max(...sleepEnds)); + recovery = await healthService.getRecoverySnapshot(recoveryStart, recoveryEnd); + } + } catch (error) { + // The deterministic local report remains useful when Health access is + // unavailable, denied, or contains no samples. + console.warn('Health data was unavailable for the wellness report:', error); + } + const previousReports = wellnessReportsRef.current.filter( + report => report.date !== date, + ); + const report = buildDailyWellnessReport({ + date, + summary, + healthSleepEntries, + recovery, + checkIn: wellnessCheckInsRef.current.find(item => item.date === date), + previousReports, }); - - // Calculate most common sleep start/end hours - const typicalSleepStart = sleepStartHours.length > 0 - ? calculateMostFrequentHour(sleepStartHours) - : TYPICAL_SLEEP_START_HOUR; - - const typicalSleepEnd = sleepEndHours.length > 0 - ? calculateMostFrequentHour(sleepEndHours) - : TYPICAL_SLEEP_END_HOUR; - - // Update sleep patterns - setSleepPatterns({ - typicalSleepStart, - typicalSleepEnd, - averageSleepDuration, + const next = [...previousReports, report] + .sort((left, right) => left.date.localeCompare(right.date)) + .slice(-90); + wellnessReportsRef.current = next; + setWellnessReports(next); + await AsyncStorage.setItem(WELLNESS_REPORTS_KEY, JSON.stringify(next)); + }, [healthService]); + + const finishSleep = useCallback(async ( + startTime: number, + endTime: number, + confidence: number, + recordStart = true, + source: SleepSession['source'] = 'app-inactivity', + evidence: SleepSession['evidence'] = [], + userConfirmed = true, + ) => { + if (endTime <= startTime) return; + const durationMinutes = (endTime - startTime) / 60_000; + const completedSession = createSleepSession({ + startTime, + endTime, + confidence, + source, + evidence, + userConfirmed, }); - }; - - // Helper to find most frequent hour - const calculateMostFrequentHour = (hours: number[]): number => { - const hourCounts = hours.reduce((counts, hour) => { - counts[hour] = (counts[hour] || 0) + 1; - return counts; - }, {} as Record); - - let mostFrequentHour = 0; - let highestCount = 0; - - Object.entries(hourCounts).forEach(([hour, count]) => { - if (count > highestCount) { - highestCount = count; - mostFrequentHour = parseInt(hour); + const nextSessions = upsertSleepSession( + sessionsRef.current, + completedSession, + ); + await persistSessions(nextSessions); + const savedSession = nextSessions.find(item => item.id === completedSession.id) + ?? nextSessions.find(item => + item.startTime === completedSession.startTime && + item.endTime === completedSession.endTime, + ) + ?? completedSession; + await appendRecords([ + ...(recordStart ? [makeRecord(SleepStatus.ASLEEP, startTime, confidence)] : []), + makeRecord(SleepStatus.AWAKE, endTime, 100), + ]); + const followUpResults = await Promise.allSettled([ + syncSleepSession(savedSession), + refreshWellnessReport(reportDateForTimestamp(endTime), true), + notificationService.notifyWakeDetected(durationMinutes, sleepQuality(durationMinutes)), + ]); + followUpResults.forEach(result => { + if (result.status === 'rejected') { + console.warn('A non-critical post-save sleep task failed:', result.reason); } }); + }, [appendRecords, notificationService, persistSessions, refreshWellnessReport, syncSleepSession]); - return mostFrequentHour; - }; - - // Calculate sleep detection confidence based on multiple factors with adaptive threshold - const calculateSleepConfidence = ( - inactiveTime: number, - currentHour: number - ): [SleepStatus, number] => { - // Base confidence from inactivity - let confidence = 0; - let status = SleepStatus.AWAKE; - - // Get adaptive threshold based on settings and time of day - let effectiveThreshold = settings.inactivityThreshold; - - if (settings.adaptiveThreshold) { - // Adjust threshold based on time of day and patterns - if (currentHour >= 22 || currentHour <= 6) { - // Nighttime - reduce threshold for faster detection - effectiveThreshold = Math.max(20, settings.inactivityThreshold - 15); - } else if (currentHour >= 13 && currentHour <= 15) { - // Typical nap time - slightly reduce threshold - effectiveThreshold = Math.max(25, settings.inactivityThreshold - 10); - } else if (currentHour >= 7 && currentHour <= 11) { - // Morning - increase threshold (less likely to be sleeping) - effectiveThreshold = settings.inactivityThreshold + 10; - } - } - - // Enhanced nap detection - const isNapTimeframe = currentHour >= 12 && currentHour <= 17; - const isNightTimeframe = currentHour >= 21 || currentHour <= 6; - - // Determine status based on adaptive threshold - if (inactiveTime >= effectiveThreshold) { - status = SleepStatus.ASLEEP; - // Calculate confidence based on how long past threshold - const excessTime = inactiveTime - effectiveThreshold; - // More gradual confidence increase over longer periods - confidence = 70 + Math.min(30, (excessTime / 60) * 30); // Scale from 70% to 100% over 60 minutes - - // Boost confidence for expected sleep times - if (isNightTimeframe) { - confidence = Math.min(100, confidence + 15); // Night sleep bonus - } else if (isNapTimeframe && settings.napDetection) { - confidence = Math.min(100, confidence + 10); // Nap detection bonus - } - } else { - status = SleepStatus.AWAKE; - // Higher confidence the more recent the activity - const proximityToThreshold = inactiveTime / effectiveThreshold; - confidence = 100 - (proximityToThreshold * 35); // Scale from 100% to 65% - - // Reduce confidence if it's expected sleep time but we're awake - if (isNightTimeframe && inactiveTime > effectiveThreshold * 0.7) { - confidence = Math.max(50, confidence - 20); // Late night activity penalty + const processInactivePeriod = useCallback(async ( + period: InactivePeriod | null, + effectiveSettings: AppSettings, + ) => { + if (!period) return; + + if (statusRef.current === SleepStatus.ASLEEP) { + const start = [...recordsRef.current] + .reverse() + .find(record => record.status === SleepStatus.ASLEEP)?.timestamp; + if (start) { + await finishSleep( + start, + period.endTime, + confidenceRef.current, + false, + 'manual', + [{ kind: 'user-confirmed', weight: 100, detail: 'The user manually started this sleep session.' }], + ); } + return; } - // Consider time of day if enabled - if (settings.considerTimeOfDay) { - const { typicalSleepStart, typicalSleepEnd } = sleepPatterns; - - // Determine if it's typical sleep hours (handle overnight periods) - const isNighttime = typicalSleepStart > typicalSleepEnd - ? (currentHour >= typicalSleepStart || currentHour < typicalSleepEnd) - : (currentHour >= typicalSleepStart && currentHour < typicalSleepEnd); - - // Adjust confidence based on time appropriateness - if (status === SleepStatus.ASLEEP) { - if (isNighttime) { - // Sleeping at night is expected - boost confidence - confidence = Math.min(100, confidence + 15); - } else { - // Sleeping during day - reduce confidence but don't penalize too much (naps are normal) - confidence = Math.max(40, confidence - 15); - } - } else { - if (isNighttime && inactiveTime > effectiveThreshold * 0.6) { - // Being awake late at night when quite inactive is suspicious - confidence = Math.max(45, confidence - 15); - } else if (!isNighttime) { - // Being awake during day is expected - confidence = Math.min(100, confidence + 10); + const session = detectSleepSession( + period.startTime, + period.endTime, + effectiveSettings.inactivityThreshold, + ); + if (session) { + let analysisStart = session.startTime; + let analysisEnd = session.endTime; + let healthEntries: SleepEntry[] = []; + const watchWindow = strongestWatchSleepWindow( + watchSnapshotsRef.current, + session.startTime, + session.endTime, + ); + let verifiedWindow = watchWindow; + let independentSleepOverlap = watchSleepOverlapRatio( + watchSnapshotsRef.current, + session.startTime, + session.endTime, + ); + if (Platform.OS === 'ios' && healthService.isAvailable()) { + try { + healthEntries = await healthService.getSleepData( + new Date(session.startTime), + new Date(session.endTime), + ); + const healthWindow = strongestHealthSleepWindow( + healthEntries, + session.startTime, + session.endTime, + ); + if (!verifiedWindow || (healthWindow?.asleepMinutes ?? 0) > verifiedWindow.asleepMinutes) { + verifiedWindow = healthWindow; + } + independentSleepOverlap = Math.max( + independentSleepOverlap, + healthSleepOverlapRatio( + healthEntries, + session.startTime, + session.endTime, + ), + ); + } catch (error) { + console.warn('Apple Health sleep evidence was unavailable:', error); } } - } - - // Additional confidence adjustments based on extended inactivity - if (status === SleepStatus.ASLEEP) { - if (inactiveTime > effectiveThreshold * 3) { - // Very long inactivity (3x threshold) strongly suggests sleep - confidence = Math.min(100, confidence + 15); - } else if (inactiveTime > effectiveThreshold * 2) { - // Extended inactivity (2x threshold) suggests sleep - confidence = Math.min(100, confidence + 10); + if (verifiedWindow) { + analysisStart = verifiedWindow.startTime; + analysisEnd = verifiedWindow.endTime; + independentSleepOverlap = Math.max( + watchSleepOverlapRatio(watchSnapshotsRef.current, analysisStart, analysisEnd), + healthSleepOverlapRatio(healthEntries, analysisStart, analysisEnd), + ); + } + const analysis = await analyzeSleepCandidate({ + startTime: analysisStart, + endTime: analysisEnd, + historicalSessions: sessionsRef.current.length > 0 + ? sessionsRef.current + : historicalSessionsFromRecords(recordsRef.current), + healthKitOverlapRatio: independentSleepOverlap, + allowLocalModel: effectiveSettings.useMachineLearning, + }); + if (analysis.classification === 'unlikely-sleep') return; + const candidate: SleepCandidate = { + id: `candidate-${analysis.startTime}-${analysis.endTime}`, + startTime: analysis.startTime, + endTime: analysis.endTime, + originalStartTime: session.startTime, + originalEndTime: session.endTime, + confidence: analysis.confidence, + classification: analysis.classification, + analysisSource: analysis.source, + explanation: analysis.explanation, + evidence: analysis.evidence, + createdAt: Date.now(), + }; + if (!candidatesRef.current.some(item => item.id === candidate.id)) { + await persistCandidates( + [...candidatesRef.current, candidate].sort((a, b) => a.startTime - b.startTime), + ); } } + }, [finishSleep, healthService, persistCandidates]); - // Ensure minimum confidence levels for decision stability - if (status === SleepStatus.ASLEEP && confidence < 60) { - confidence = 60; // Minimum 60% confidence for sleep detection - } else if (status === SleepStatus.AWAKE && confidence < 70) { - confidence = 70; // Minimum 70% confidence for awake detection - } - - console.log(`🧠 Adaptive detection: threshold ${effectiveThreshold}min (base: ${settings.inactivityThreshold}min), inactive: ${Math.round(inactiveTime)}min, confidence: ${Math.round(confidence)}%`); - - return [status, Math.round(Math.max(0, Math.min(100, confidence)))]; - }; - - // Calculate sleep quality based on duration - const calculateSleepQuality = (sleepDuration: number): string => { - if (sleepDuration >= 480) { // 8+ hours - return 'Excellent'; - } else if (sleepDuration >= 420) { // 7+ hours - return 'Good'; - } else if (sleepDuration >= 360) { // 6+ hours - return 'Adequate'; - } else if (sleepDuration >= 300) { // 5+ hours - return 'Poor'; - } else { - return 'Insufficient'; - } - }; - - // Periodic sleep detection check using background service useEffect(() => { - const checkSleepStatus = async () => { - try { - // Get inactivity duration, excluding the current app check session - const inactiveMinutes = await backgroundService.getInactivityDuration(false); - const currentHour = new Date().getHours(); - const now = Date.now(); - - // CRITICAL FIX: Check for threshold crossing event from background service - try { - const thresholdEvent = await AsyncStorage.getItem('SLEEP_THRESHOLD_CROSSED'); - if (thresholdEvent) { - const event = JSON.parse(thresholdEvent); - // Process threshold crossing events if we're currently awake and event is recent - const eventAge = (now - event.timestamp) / (1000 * 60); // minutes - - if (event.detected && eventAge < 10 && currentStatus === SleepStatus.AWAKE) { - // Use the sleep start time calculated by background service if available - const sleepStartTime = event.sleepStartTime || (event.timestamp - ((event.inactiveMinutes - settings.inactivityThreshold) * 60 * 1000)); - - // Record sleep with accurate start time and high confidence - addActivityRecord(SleepStatus.ASLEEP, sleepStartTime, 90); - - // Send sleep detection notification if not already sent - if (!event.notificationSent) { - await notificationService.notifySleepDetected(event.inactiveMinutes); - } - - // Clear the event so we don't process it again - await AsyncStorage.removeItem('SLEEP_THRESHOLD_CROSSED'); - console.log(`✅ Threshold crossing event processed and cleared`); - - return; // Exit early - we've handled the threshold crossing - } else if (eventAge >= 10) { - // Clean up old events - await AsyncStorage.removeItem('SLEEP_THRESHOLD_CROSSED'); - console.log(`🧹 Cleaned up old threshold event (${Math.round(eventAge)}min old)`); - } - } - } catch (thresholdError) { - console.error('Error checking threshold crossing event:', thresholdError); - } - - // Calculate sleep status and confidence based on inactivity - const [detectedStatus, confidence] = calculateSleepConfidence(inactiveMinutes, currentHour); - - // ENHANCED THRESHOLD DETECTION: Immediate sleep recording when threshold is reached - if (inactiveMinutes >= settings.inactivityThreshold && currentStatus === SleepStatus.AWAKE) { - console.log(`🛌 SLEEP THRESHOLD REACHED: ${Math.round(inactiveMinutes)} minutes >= ${settings.inactivityThreshold} minutes`); - - // Calculate accurate sleep start time (when threshold was reached) - const sleepStartTime = now - (settings.inactivityThreshold * 60 * 1000); + let cancelled = false; - // Record sleep with the correct start time - addActivityRecord(SleepStatus.ASLEEP, sleepStartTime, confidence); - console.log(`😴 Sleep auto-recorded from foreground check - start: ${new Date(sleepStartTime).toLocaleTimeString()}, current duration: ${Math.round(inactiveMinutes)}min`); - - // Send sleep detection notification - await notificationService.notifySleepDetected(inactiveMinutes); - - // Also trigger a force threshold check to ensure background service is in sync - try { - await backgroundService.forceThresholdCheck(); - } catch (error) { - console.error('Error in force threshold check:', error); - } - - return; // Exit early to avoid duplicate processing - } - - // FALLBACK: Force threshold check if we're close to threshold (90% of threshold) - const closeToThreshold = inactiveMinutes >= (settings.inactivityThreshold * 0.9); - if (closeToThreshold && currentStatus === SleepStatus.AWAKE) { - console.log(`⚠️ Close to threshold: ${Math.round(inactiveMinutes)}min (${Math.round((inactiveMinutes / settings.inactivityThreshold) * 100)}% of ${settings.inactivityThreshold}min threshold)`); - - // Force a background threshold check - try { - const thresholdTriggered = await backgroundService.forceThresholdCheck(); - if (thresholdTriggered) { - console.log(`🚨 Force threshold check triggered sleep detection`); - return; // Exit to let the event be processed on next cycle - } - } catch (error) { - console.error('Error in force threshold check:', error); - } + const load = async () => { + try { + const [ + recordsJson, + settingsJson, + candidatesJson, + sessionsJson, + wellnessReportsJson, + wellnessCheckInsJson, + ] = await Promise.all([ + AsyncStorage.getItem(ACTIVITY_RECORDS_KEY), + AsyncStorage.getItem(SETTINGS_KEY), + AsyncStorage.getItem(SLEEP_CANDIDATES_KEY), + AsyncStorage.getItem(SLEEP_SESSIONS_KEY), + AsyncStorage.getItem(WELLNESS_REPORTS_KEY), + AsyncStorage.getItem(WELLNESS_CHECK_INS_KEY), + ]); + if (cancelled) return; + + const loadedRecords = validActivityRecords( + parseStoredJson(recordsJson, 'activity record'), + ); + const loadedSettings = normalizedSettings( + parseStoredJson(settingsJson, 'settings'), + ); + const loadedCandidates = validSleepCandidates( + parseStoredJson(candidatesJson, 'sleep candidate'), + ); + const parsedSessions = parseStoredJson(sessionsJson, 'sleep session'); + const loadedSessions = parsedSessions === undefined + ? historicalSessionsFromRecords(loadedRecords) + : validSleepSessions(parsedSessions); + const loadedWellnessReports = validDatedItems( + parseStoredJson(wellnessReportsJson, 'wellness report'), + ); + const loadedWellnessCheckIns = validDatedItems( + parseStoredJson(wellnessCheckInsJson, 'wellness check-in'), + ); + const latest = [...loadedRecords].sort((a, b) => b.timestamp - a.timestamp)[0]; + + recordsRef.current = loadedRecords; + setActivityRecords(loadedRecords); + candidatesRef.current = loadedCandidates; + setSleepCandidates(loadedCandidates); + sessionsRef.current = loadedSessions; + setSleepSessions(loadedSessions); + wellnessReportsRef.current = loadedWellnessReports; + wellnessCheckInsRef.current = loadedWellnessCheckIns; + setWellnessReports(loadedWellnessReports); + settingsRef.current = loadedSettings; + setSettings(loadedSettings); + setSettingsLoaded(true); + if (latest) { + statusRef.current = latest.status; + confidenceRef.current = latest.confidence; + setCurrentStatus(latest.status); + setCurrentConfidence(latest.confidence); } - const wakeThreshold = Math.min(15, settings.inactivityThreshold * 0.4); // More responsive wake detection - if (inactiveMinutes < wakeThreshold && currentStatus === SleepStatus.ASLEEP) { - // Record wake time - addActivityRecord(SleepStatus.AWAKE, now, confidence); - - // Calculate sleep duration and quality for wake notification - const sleepDuration = getTodaySleepDuration(); - const sleepQuality = calculateSleepQuality(sleepDuration); - await notificationService.notifyWakeDetected(sleepDuration, sleepQuality); - - return; // Exit early + await backgroundService.startMonitoring(); + const pendingPeriod = await backgroundService.consumeInactivePeriod(Date.now()); + await Promise.all([ + notificationService.initialize(), + healthService.initialize(false), + ]); + try { + watchSnapshotsRef.current = await WatchDataService.consumePendingSnapshots(); + } catch (error) { + console.warn('Unable to load Apple Watch sleep evidence:', error); } + notificationService.updateAppSettings(loadedSettings); - // Only update if status changed or confidence changed significantly (>15%) - const significantConfidenceChange = Math.abs(confidence - currentConfidence) > 15; - const statusChanged = detectedStatus !== currentStatus; + await syncHealthSessions(); + await refreshWellnessReport(); - if (statusChanged || significantConfidenceChange) { - if (statusChanged) { - setCurrentStatus(detectedStatus); - setCurrentConfidence(confidence); - } else if (significantConfidenceChange) { - setCurrentConfidence(confidence); - } + await processInactivePeriod(pendingPeriod, loadedSettings); + initializedRef.current = true; + if (AppState.currentState === 'background') { + await backgroundService.recordAppInactive(Date.now()); } } catch (error) { - console.error('❌ Error checking sleep status:', error); + console.error('Failed to initialize sleep tracking:', error); + setSettingsLoaded(true); + initializedRef.current = true; } }; - const interval = setInterval(checkSleepStatus, 30 * 1000); - - // Initial check after a short delay - const initialTimeout = setTimeout(checkSleepStatus, 2000); - + load(); return () => { - clearInterval(interval); - clearTimeout(initialTimeout); + cancelled = true; + backgroundService.stopMonitoring().catch(console.error); }; - }, [currentStatus, currentConfidence, settings.inactivityThreshold, settings.considerTimeOfDay]); - // Handle app state changes (enhanced with background service integration) - useEffect(() => { - const handleAppStateChange = async (nextAppState: AppStateStatus) => { - const now = Date.now(); - const currentHour = new Date().getHours(); - - if (nextAppState === 'active') { - // App became active - record genuine user activity - backgroundService.recordUserActivity(); - - // Check if there was a pending threshold crossing event that we need to clear - const hadThresholdEvent = await backgroundService.hasThresholdCrossingEvent(); - if (hadThresholdEvent) { - console.log('🚨 User became active with pending sleep detection - clearing threshold event'); - await backgroundService.clearThresholdCrossingEvent(); - } - - // Get actual inactivity time from background service - const inactiveTime = await backgroundService.getInactivityDuration(false); - console.log(`📱 App became active after ${Math.round(inactiveTime)} minutes of inactivity`); - - // ENHANCED WAKE DETECTION: More responsive wake detection - if (currentStatus === SleepStatus.ASLEEP) { - // User was sleeping and just became active - this is a wake event - console.log('☀️ WAKE DETECTED: User opened app while sleeping'); - - addActivityRecord(SleepStatus.AWAKE, now, 95); // High confidence for active app usage - - // Send wake detection notification with sleep summary - const sleepDuration = getTodaySleepDuration(); - const sleepQuality = calculateSleepQuality(sleepDuration); - await notificationService.notifyWakeDetected(sleepDuration, sleepQuality); - - } else if (inactiveTime >= settings.inactivityThreshold) { - // If we were inactive for longer than the threshold, we missed the sleep detection - console.log(`🛌 MISSED SLEEP DETECTION: App became active after ${Math.round(inactiveTime)}min (threshold: ${settings.inactivityThreshold}min)`); - - const [detectedStatus, confidence] = calculateSleepConfidence(inactiveTime, currentHour); - - if (detectedStatus === SleepStatus.ASLEEP) { - // Record retroactive sleep period - const fellAsleepAt = now - (inactiveTime * 60 * 1000) + (settings.inactivityThreshold * 60 * 1000); - addActivityRecord(SleepStatus.ASLEEP, fellAsleepAt, confidence); - - // Then record that we're now awake - addActivityRecord(SleepStatus.AWAKE, now, 95); + }, [backgroundService, healthService, notificationService, processInactivePeriod, refreshWellnessReport, syncHealthSessions]); - // Send wake notification with sleep summary - const sleepDuration = getTodaySleepDuration(); - const sleepQuality = calculateSleepQuality(sleepDuration); - await notificationService.notifyWakeDetected(sleepDuration, sleepQuality); + useEffect(() => { + const subscription = AppState.addEventListener('change', nextState => { + const previousState = appStateRef.current; + const transitionAt = Date.now(); + appStateRef.current = nextState; + if (!initializedRef.current) return; + + lifecycleQueueRef.current = lifecycleQueueRef.current + .then(async () => { + if (nextState === 'active' && previousState !== 'active') { + const period = await backgroundService.consumeInactivePeriod(transitionAt); + // Keep lifecycle persistence independent from HealthKit, Watch, and + // model latency so a quick re-background cannot lose its boundary. + resumeWorkQueueRef.current = resumeWorkQueueRef.current + .then(async () => { + try { + watchSnapshotsRef.current = await WatchDataService.consumePendingSnapshots(); + } catch (error) { + console.warn('Unable to refresh Apple Watch sleep evidence:', error); + } + await processInactivePeriod(period, settingsRef.current); + await syncHealthSessions(); + await refreshWellnessReport(); + }) + .catch(error => { + console.error('Failed to refresh foreground sleep data:', error); + }); + } else if (nextState === 'background' && previousState !== 'background') { + await backgroundService.recordAppInactive(transitionAt); } - } else { - // Just a normal app activation - record minor activity update - console.log(`📱 Normal app activation after ${Math.round(inactiveTime)} minutes`); - } - - } else if (nextAppState === 'background' || nextAppState === 'inactive') { - // App went to background - background service will continue monitoring - setLastActiveTimestamp(now); - console.log('📵 App went to background - background monitoring will continue'); - } - }; - - // Subscribe to app state changes - const subscription = AppState.addEventListener('change', handleAppStateChange); - - // Initial activity record if none exists - if (activityRecords.length === 0) { - addActivityRecord(SleepStatus.AWAKE); - } - - return () => { - subscription.remove(); - }; - }, [currentStatus, lastActiveTimestamp, settings.inactivityThreshold, settings.considerTimeOfDay]); + }) + .catch(error => { + console.error('Failed to process app activity transition:', error); + }); + }); + return () => subscription.remove(); + }, [backgroundService, processInactivePeriod, refreshWellnessReport, syncHealthSessions]); - // Add a new activity record - const addActivityRecord = ( - status: SleepStatus, - timestamp = Date.now(), - confidence = 100 - ) => { - const newRecord: ActivityRecord = { - id: `${timestamp}-${Math.random().toString(36).substr(2, 9)}`, - timestamp, - status, - confidence: confidence, - }; + useEffect(() => { + if (!settingsLoaded) return; + AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)).catch(console.error); + }, [settings, settingsLoaded]); - setActivityRecords(prev => [...prev, newRecord]); - setCurrentStatus(status); - setCurrentConfidence(confidence); + useEffect(() => { + if (!settingsLoaded) return; + AsyncStorage.setItem(DAILY_SUMMARIES_KEY, JSON.stringify(dailySummaries)).catch(console.error); + }, [dailySummaries, settingsLoaded]); + + const updateSettings = (changes: Partial) => { + const updated = { ...settingsRef.current, ...changes }; + settingsRef.current = updated; + setSettings(updated); + notificationService.updateAppSettings(updated); }; - // Allow manual setting of sleep status const manuallySetStatus = async (status: SleepStatus) => { - const previousStatus = currentStatus; - - // Add record with high confidence since user manually set it - addActivityRecord(status, Date.now(), 100); - - // Also record this as genuine user activity in the background service - backgroundService.recordUserActivity(); - - // Handle notifications for manual status changes - try { - if (status === SleepStatus.AWAKE && previousStatus === SleepStatus.ASLEEP) { - // User manually marked as awake - send wake notification - const sleepDuration = getTodaySleepDuration(); - const sleepQuality = calculateSleepQuality(sleepDuration); - await notificationService.notifyWakeDetected(sleepDuration, sleepQuality); - console.log('Wake notification sent for manual status change'); + if (statusRef.current === status) return; + const now = Date.now(); + if (status === SleepStatus.AWAKE) { + const start = [...recordsRef.current] + .reverse() + .find(record => record.status === SleepStatus.ASLEEP)?.timestamp; + if (start) { + await finishSleep( + start, + now, + 100, + false, + 'manual', + [{ kind: 'user-confirmed', weight: 100, detail: 'The user manually marked this sleep session.' }], + ); } - // Note: We don't send sleep detection notification for manual sleep setting - // since the user is actively using the phone to set the status - } catch (error) { - console.error('Failed to send notification for manual status change:', error); + } else { + await appendRecords([makeRecord(SleepStatus.ASLEEP, now, 100)]); } + await backgroundService.recordUserActivity(now); }; - // Update daily summaries based on activity records - const updateDailySummaries = () => { - // Group records by day - const recordsByDay: Record = {}; - - activityRecords.forEach(record => { - const date = format(record.timestamp, 'yyyy-MM-dd'); - if (!recordsByDay[date]) { - recordsByDay[date] = []; - } - recordsByDay[date].push(record); - }); - - // Calculate sleep periods and totals for each day - const newSummaries: DailySleepSummary[] = Object.keys(recordsByDay).map(date => { - const dayRecords = recordsByDay[date].sort((a, b) => a.timestamp - b.timestamp); - const sleepPeriods: { start: number; end: number; confidence: number }[] = []; - let totalSleepMinutes = 0; - - // Find sleep periods - for (let i = 0; i < dayRecords.length - 1; i++) { - if (dayRecords[i].status === SleepStatus.ASLEEP && dayRecords[i + 1].status === SleepStatus.AWAKE) { - const start = dayRecords[i].timestamp; - const end = dayRecords[i + 1].timestamp; - // Average confidence between sleep and wake events - const confidence = (dayRecords[i].confidence + dayRecords[i + 1].confidence) / 2; - - sleepPeriods.push({ start, end, confidence }); - - // Add to total sleep time (weighted by confidence) - const periodMinutes = (end - start) / (1000 * 60); - totalSleepMinutes += periodMinutes * (confidence / 100); - } - } - - return { - date, - totalSleepMinutes, - sleepPeriods, - }; - }); - - setDailySummaries(newSummaries); + const confirmSleepCandidate = async (): Promise => { + if (candidateActionRef.current) return; + const candidate = candidatesRef.current[0]; + if (!candidate) return; + candidateActionRef.current = true; + try { + const independentEvidence = candidate.evidence?.some(item => + item.kind === 'healthkit-sleep' || + item.kind === 'watch-low-motion' || + item.kind === 'watch-heart-rate', + ); + await finishSleep( + candidate.startTime, + candidate.endTime, + candidate.confidence, + true, + independentEvidence ? 'combined' : 'app-inactivity', + [ + ...(candidate.evidence ?? []), + { kind: 'user-confirmed', weight: 100, detail: 'The user confirmed this estimated sleep period.' }, + ], + ); + await persistCandidates( + candidatesRef.current.filter(item => item.id !== candidate.id), + ); + } finally { + candidateActionRef.current = false; + } }; - // Update settings and pass to services - const updateSettings = (newSettings: Partial) => { - const updatedSettings = { ...settings, ...newSettings }; - setSettings(updatedSettings); + const dismissSleepCandidate = async (): Promise => { + if (candidateActionRef.current) return; + const candidate = candidatesRef.current[0]; + if (!candidate) return; + candidateActionRef.current = true; + try { + await persistCandidates( + candidatesRef.current.filter(item => item.id !== candidate.id), + ); + } finally { + candidateActionRef.current = false; + } + }; - // Update notification service settings for contextual notifications - notificationService.updateAppSettings(updatedSettings); + const updateSleepCandidateBounds = (startTime: number, endTime: number): void => { + if ( + !Number.isFinite(startTime) || + !Number.isFinite(endTime) || + endTime <= startTime || + endTime - startTime > 20 * 60 * 60_000 + ) return; + const next: SleepCandidate[] = candidatesRef.current.map((candidate, index) => + index === 0 + ? { + ...candidate, + startTime, + endTime, + evidence: [ + ...(candidate.evidence ?? []).filter(item => item.kind !== 'user-edited'), + { kind: 'user-edited' as const, weight: 100, detail: 'The user adjusted the estimated sleep boundaries.' }, + ], + } + : candidate, + ); + void persistCandidates(next).catch(console.error); }; - // Export data as JSON - const exportData = async (): Promise => { - const data = { - activityRecords, - dailySummaries, - settings, - sleepPatterns, - exportDate: new Date().toISOString(), + const saveWellnessCheckIn = async ( + checkIn: Omit, + ): Promise => { + const item: DailyWellnessCheckIn = { + ...checkIn, + date: format(new Date(), 'yyyy-MM-dd'), + updatedAt: Date.now(), }; + const next = [ + ...wellnessCheckInsRef.current.filter(existing => existing.date !== item.date), + item, + ].sort((left, right) => left.date.localeCompare(right.date)).slice(-90); + wellnessCheckInsRef.current = next; + await AsyncStorage.setItem(WELLNESS_CHECK_INS_KEY, JSON.stringify(next)); + await refreshWellnessReport(item.date, true); + }; - return JSON.stringify(data, null, 2); + const clearSleepData = async (): Promise => { + recordsRef.current = []; + statusRef.current = SleepStatus.AWAKE; + confidenceRef.current = 100; + setActivityRecords([]); + candidatesRef.current = []; + setSleepCandidates([]); + sessionsRef.current = []; + setSleepSessions([]); + wellnessReportsRef.current = []; + wellnessCheckInsRef.current = []; + setWellnessReports([]); + setCurrentStatus(SleepStatus.AWAKE); + setCurrentConfidence(100); + await Promise.all([ + AsyncStorage.multiRemove([ + ACTIVITY_RECORDS_KEY, + DAILY_SUMMARIES_KEY, + SLEEP_PATTERNS_KEY, + SLEEP_CANDIDATES_KEY, + SLEEP_SESSIONS_KEY, + WELLNESS_REPORTS_KEY, + WELLNESS_CHECK_INS_KEY, + ]), + backgroundService.resetTrackingState(), + WatchDataService.clear(), + ]); }; - // Get today's sleep duration in minutes - const getTodaySleepDuration = (): number => { + const todayRecords = useMemo(() => { const today = format(new Date(), 'yyyy-MM-dd'); - const todaySummary = dailySummaries.find(summary => summary.date === today); - return todaySummary?.totalSleepMinutes || 0; - }; + return activityRecords.filter(record => format(record.timestamp, 'yyyy-MM-dd') === today); + }, [activityRecords]); - // Get today's activity records - const getTodayRecords = (): ActivityRecord[] => { + const getTodaySleepDuration = (): number => { const today = format(new Date(), 'yyyy-MM-dd'); - return activityRecords.filter(record => - format(record.timestamp, 'yyyy-MM-dd') === today - ); + return dailySummaries.find(summary => summary.date === today)?.totalSleepMinutes ?? 0; }; - // Test method to check current sleep detection status (for debugging) - const testSleepDetection = async (): Promise<{ - inactivityMinutes: number; - currentHour: number; - detectedStatus: SleepStatus; - confidence: number; - threshold: number; - }> => { - const inactivityMinutes = await backgroundService.getInactivityDuration(false); - const currentHour = new Date().getHours(); - const [detectedStatus, confidence] = calculateSleepConfidence(inactivityMinutes, currentHour); - - return { - inactivityMinutes, - currentHour, - detectedStatus, - confidence, - threshold: settings.inactivityThreshold - }; - }; + const exportData = async (): Promise => JSON.stringify({ + activityRecords, + dailySummaries, + sleepCandidates, + sleepSessions, + wellnessReports, + settings, + exportDate: new Date().toISOString(), + }, null, 2); return ( - + {children} ); }; -// Custom hook to use the sleep context export const useSleep = (): SleepContextType => { const context = useContext(SleepContext); - if (context === undefined) { - throw new Error('useSleep must be used within a SleepProvider'); - } + if (!context) throw new Error('useSleep must be used within a SleepProvider'); return context; -}; \ No newline at end of file +}; diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx index 25bc5bc..0021506 100644 --- a/src/context/ThemeContext.tsx +++ b/src/context/ThemeContext.tsx @@ -94,9 +94,8 @@ interface ThemeProviderProps { export const ThemeProvider: React.FC = ({ children }) => { const [themeMode, setThemeModeState] = useState('auto'); const [systemColorScheme, setSystemColorScheme] = useState( - Appearance.getColorScheme() + Appearance.getColorScheme() ?? 'unspecified' ); - const [forceUpdate, setForceUpdate] = useState(0); // <--- add force update state // Load saved theme preference useEffect(() => { @@ -118,7 +117,6 @@ export const ThemeProvider: React.FC = ({ children }) => { useEffect(() => { const subscription = Appearance.addChangeListener(({ colorScheme }) => { setSystemColorScheme(colorScheme); - setForceUpdate((f) => f + 1); // <--- force re-render on system change }); return () => subscription?.remove(); }, []); @@ -136,7 +134,6 @@ export const ThemeProvider: React.FC = ({ children }) => { try { setThemeModeState(mode); await AsyncStorage.setItem(THEME_STORAGE_KEY, mode); - setForceUpdate((f) => f + 1); // <--- force re-render on theme change console.log(`Theme mode saved and applied: ${mode}`); } catch (error) { console.error('Failed to save theme preference:', error); @@ -153,8 +150,7 @@ export const ThemeProvider: React.FC = ({ children }) => { setThemeMode, }} > - {/* force update children on theme change */} - {children} + {children} ); }; diff --git a/src/screens/NotificationSettingsScreen.tsx b/src/screens/NotificationSettingsScreen.tsx index 40b43aa..6fd4cf2 100644 --- a/src/screens/NotificationSettingsScreen.tsx +++ b/src/screens/NotificationSettingsScreen.tsx @@ -14,9 +14,9 @@ interface NotificationSettings { export const NotificationSettingsScreen = () => { const { colors } = useTheme(); const [settings, setSettings] = useState({ - sleepDetectionEnabled: true, - wakeDetectionEnabled: true, - sleepRemindersEnabled: true, + sleepDetectionEnabled: false, + wakeDetectionEnabled: false, + sleepRemindersEnabled: false, bedtimeReminderHour: 22, }); const [notificationService] = useState(() => NotificationService.getInstance()); @@ -38,9 +38,13 @@ export const NotificationSettingsScreen = () => { }; const updateSetting = async (key: keyof NotificationSettings, value: any) => { + const previousSettings = settings; try { + if (value === true && !(await notificationService.requestPermissions())) { + Alert.alert('Permission Needed', 'Enable notifications in iOS Settings to use this option.'); + return; + } const newSettings = { ...settings, [key]: value }; - setSettings(newSettings); await notificationService.updateSettings({ [key]: value }); // Handle special cases @@ -51,8 +55,11 @@ export const NotificationSettingsScreen = () => { await notificationService.cancelBedtimeReminder(); } } + setSettings(newSettings); } catch (error) { console.error('Failed to update notification setting:', error); + await notificationService.updateSettings(previousSettings).catch(console.error); + setSettings(previousSettings); Alert.alert('Error', 'Failed to update notification settings'); } }; @@ -95,33 +102,15 @@ export const NotificationSettingsScreen = () => { - Sleep Detection - - - - - - Sleep Detection Alerts - - Get notified when inactivity-based sleep is detected - - - - updateSetting('sleepDetectionEnabled', value)} - trackColor={{ false: colors.border, true: colors.primary + '40' }} - thumbColor={settings.sleepDetectionEnabled ? colors.primary : colors.textSecondary} - /> - + Sleep Summaries - Wake Detection Alerts + Confirmed Sleep Summary - Get a summary when you wake up with sleep duration and quality + Get a duration summary after you confirm a sleep period @@ -183,10 +172,7 @@ export const NotificationSettingsScreen = () => { ℹ️ About Notifications - • Sleep detection notifications are sent when you've been inactive for your set threshold - - - • Wake notifications include your sleep duration and quality assessment + • Summary notifications are sent only after you confirm a sleep estimate • Bedtime reminders help maintain consistent sleep schedules diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index dffde0f..10f8137 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, Switch, TouchableOpacity, Alert, ScrollView, Modal, FlatList, Linking } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; +import Constants from 'expo-constants'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { RootStackParamList } from '../types'; @@ -46,9 +47,12 @@ const THEME_OPTIONS = [ { label: 'Dark', value: 'dark' as ThemeMode }, ]; +// Keep unfinished controls out of production until they affect the detector. +const SHOW_EXPERIMENTAL_DETECTION_SETTINGS = false; + const SettingsScreen = () => { const navigation = useNavigation(); - const { settings, updateSettings } = useSleep(); + const { settings, updateSettings, clearSleepData } = useSleep(); const { themeMode, setThemeMode, colors, isDarkMode } = useTheme(); const [useMachineLearning, setUseMachineLearning] = useState(settings.useMachineLearning); const [considerTimeOfDay, setConsiderTimeOfDay] = useState(settings.considerTimeOfDay); @@ -72,6 +76,7 @@ const SettingsScreen = () => { // Create themed styles const themedStyles = createThemedStyles(colors); + const appVersion = Constants.expoConfig?.version ?? '1.4.1'; // Sync local state with context settings when they change useEffect(() => { @@ -79,8 +84,8 @@ const SettingsScreen = () => { if (!settingsInitialized || useMachineLearning !== settings.useMachineLearning || considerTimeOfDay !== settings.considerTimeOfDay || - adaptiveThreshold !== (settings.adaptiveThreshold || true) || - napDetection !== (settings.napDetection || true) || + adaptiveThreshold !== (settings.adaptiveThreshold ?? true) || + napDetection !== (settings.napDetection ?? true) || smartWakeupWindow !== (settings.smartWakeupWindow ?? true) || confidenceBasedAdjustment !== (settings.confidenceBasedAdjustment ?? true) || contextualNotifications !== (settings.contextualNotifications ?? true) || @@ -91,8 +96,8 @@ const SettingsScreen = () => { setUseMachineLearning(settings.useMachineLearning); setConsiderTimeOfDay(settings.considerTimeOfDay); - setAdaptiveThreshold(settings.adaptiveThreshold || true); - setNapDetection(settings.napDetection || true); + setAdaptiveThreshold(settings.adaptiveThreshold ?? true); + setNapDetection(settings.napDetection ?? true); // New advanced settings synchronization setSmartWakeupWindow(settings.smartWakeupWindow ?? true); setConfidenceBasedAdjustment(settings.confidenceBasedAdjustment ?? true); @@ -254,12 +259,7 @@ const SettingsScreen = () => { style: 'destructive', onPress: async () => { try { - // Only clear sleep-related data, not settings - await AsyncStorage.multiRemove([ - 'sleep-tracker-activity-records', - 'sleep-tracker-daily-summaries', - 'sleep-tracker-patterns' - ]); + await clearSleepData(); Alert.alert('Success', 'All sleep data has been reset.'); } catch (error) { console.error('Error resetting data:', error); @@ -298,8 +298,17 @@ const SettingsScreen = () => { ); }; + const openExternalLink = async (url: string): Promise => { + try { + await Linking.openURL(url); + } catch (error) { + console.error('Unable to open external link:', error); + Alert.alert('Unable to Open Link', 'Please try again when your device can open web links.'); + } + }; + const openMealSnapApp = (): void => { - Linking.openURL('https://apps.apple.com/app/mealsnap-ai-food-log-tracker/id6475162854'); + void openExternalLink('https://apps.apple.com/app/mealsnap-ai-food-log-tracker/id6475162854'); }; return ( @@ -313,7 +322,7 @@ const SettingsScreen = () => { Inactivity Threshold - Base inactivity time before considered asleep + Time away before a probable sleep candidate is created for your confirmation @@ -322,7 +331,7 @@ const SettingsScreen = () => { - setShowSensitivityModal(true)} > @@ -336,9 +345,9 @@ const SettingsScreen = () => { {currentSensitivityOption.label} - + } - setShowPersistenceModal(true)} > @@ -352,10 +361,10 @@ const SettingsScreen = () => { {currentPersistenceOption.label} - + } - + {SHOW_EXPERIMENTAL_DETECTION_SETTINGS && Adaptive Detection @@ -386,9 +395,9 @@ const SettingsScreen = () => { Optimized detection for short afternoon naps and unusual sleep times - + } - + {SHOW_EXPERIMENTAL_DETECTION_SETTINGS && Accuracy Settings @@ -419,9 +428,9 @@ const SettingsScreen = () => { Factor in typical sleep hours for better detection - + } - + {SHOW_EXPERIMENTAL_DETECTION_SETTINGS && Advanced Settings @@ -482,9 +491,9 @@ const SettingsScreen = () => { Use enhanced multi-layer sensor filtering for better accuracy - + } - + {SHOW_EXPERIMENTAL_DETECTION_SETTINGS && Power & Lifestyle @@ -530,7 +539,7 @@ const SettingsScreen = () => { Validate and clean sleep data for improved reliability - + } Appearance @@ -580,7 +589,7 @@ const SettingsScreen = () => { - + {__DEV__ && Debug & Monitoring { )} - + } About Sleep Detector - Version 1.0.0 + Version {appVersion} - Track your sleep patterns based on phone usage. + Review probable sleep periods estimated from time away from Sleep Detector. + + + + + + + FOUNDER · PRODUCT VISION · ENGINEERING + Pierre-Henry Soria + + + + Pierre-Henry conceived the idea, brought its vision and clarity, and designed and built + the systems behind Sleep Detector to solve the painful gap between phone inactivity and + useful, reviewable sleep insight. + + + Building the app’s systems since early 2024. + + + void openExternalLink('https://pierrehenry.dev')} + > + + Website + + void openExternalLink('https://github.com/pH-7')} + > + + GitHub + + void openExternalLink('https://www.linkedin.com/in/ph7enry/')} + > + + LinkedIn + + + MealSnap helps with your eating habits @@ -759,7 +816,7 @@ const SettingsScreen = () => { /> - This is how long your phone needs to be inactive before Sleep Detector considers you asleep. + This is how long you must be away from Sleep Detector before it creates a probable sleep period for review. @@ -918,6 +975,81 @@ const createThemedStyles = (colors: any) => StyleSheet.create({ marginTop: 8, textAlign: 'center', }, + creatorCard: { + alignSelf: 'stretch', + marginTop: 18, + padding: 16, + borderRadius: 16, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.primary + '35', + }, + creatorHeader: { + flexDirection: 'row', + alignItems: 'center', + }, + creatorIcon: { + width: 42, + height: 42, + borderRadius: 21, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.primary + '18', + marginRight: 12, + }, + creatorIdentity: { + flex: 1, + }, + creatorRole: { + fontSize: 10, + lineHeight: 14, + letterSpacing: 0.7, + fontWeight: '700', + color: colors.primary, + }, + creatorName: { + marginTop: 2, + fontSize: 19, + fontWeight: '700', + color: colors.text, + }, + creatorStory: { + marginTop: 14, + fontSize: 13, + lineHeight: 19, + color: colors.textSecondary, + }, + creatorSince: { + marginTop: 10, + fontSize: 13, + fontWeight: '600', + color: colors.text, + }, + creatorLinks: { + flexDirection: 'row', + flexWrap: 'wrap', + marginTop: 14, + marginHorizontal: -4, + }, + creatorLink: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + minWidth: 88, + flexGrow: 1, + paddingHorizontal: 10, + paddingVertical: 9, + marginHorizontal: 4, + marginBottom: 8, + borderRadius: 10, + backgroundColor: colors.primary + '12', + }, + creatorLinkText: { + marginLeft: 6, + fontSize: 12, + fontWeight: '600', + color: colors.primary, + }, dropdownButton: { flexDirection: 'row', justifyContent: 'space-between', @@ -1172,4 +1304,4 @@ const styles = StyleSheet.create({ }, }); -export default SettingsScreen; \ No newline at end of file +export default SettingsScreen; diff --git a/src/screens/SleepDetailsScreen.tsx b/src/screens/SleepDetailsScreen.tsx index f0171ef..bed8979 100644 --- a/src/screens/SleepDetailsScreen.tsx +++ b/src/screens/SleepDetailsScreen.tsx @@ -77,7 +77,7 @@ const SleepDetailsScreen = ({ route }: SleepDetailsScreenProps) => { - Sleep Quality + Duration Assessment {getSleepQualityInfo(sleepSummary.totalSleepMinutes, colors)} @@ -252,4 +252,4 @@ const createThemedStyles = (colors: any) => StyleSheet.create({ }, }); -export default SleepDetailsScreen; \ No newline at end of file +export default SleepDetailsScreen; diff --git a/src/screens/StatsScreen.tsx b/src/screens/StatsScreen.tsx index 2814953..864d367 100644 --- a/src/screens/StatsScreen.tsx +++ b/src/screens/StatsScreen.tsx @@ -4,8 +4,7 @@ import { BarChart } from 'react-native-chart-kit'; import { useSleep } from '../context/SleepContext'; import { useTheme } from '../context/ThemeContext'; -import { formatDuration } from '../utils/dateUtils'; -import { getPastWeekDates, getPastMonthDates, getDayOfWeek } from '../utils/dateUtils'; +import { formatDuration, getPastWeekDates, getPastMonthDates, getDayOfWeek } from '../utils/dateUtils'; const { width } = Dimensions.get('window'); @@ -24,8 +23,6 @@ const StatsScreen = () => { // Update chart data when time range or daily summaries change useEffect(() => { - if (dailySummaries.length === 0) return; - // Get date range based on selected time range const dateRange = timeRange === 'week' ? getPastWeekDates() : getPastMonthDates(); @@ -75,6 +72,7 @@ const StatsScreen = () => { const filteredSummaries = dailySummaries.filter( summary => dateRange.includes(summary.date) ); + if (filteredSummaries.length === 0) return { consistency: 0, efficiency: 0 }; // Calculate sleep consistency (lower standard deviation is better) const sleepDurations = filteredSummaries.map(s => s.totalSleepMinutes / 60); @@ -124,9 +122,6 @@ const StatsScreen = () => { style: { borderRadius: 8, }, - propsForLabels: { - fontSize: timeRange === 'week' ? 12 : 10, // Smaller font for month view - }, formatXLabel: (value: string) => { // For month view, ensure we don't show overlapping labels if (timeRange === 'month' && value === '') { @@ -147,6 +142,9 @@ const StatsScreen = () => { ) }] }; + const hasSleepData = enhancedChartData.datasets[0].data.some( + (hours: number) => hours > 0, + ); return ( @@ -180,7 +178,7 @@ const StatsScreen = () => { Sleep Duration - {enhancedChartData.datasets[0].data.length > 0 ? ( + {hasSleepData ? ( { - Efficiency + 7h+ nights { • Have a sleep consistency of {Math.round(metrics.consistency)}% - • Achieve good sleep {Math.round(metrics.efficiency)}% of the time + • Reach 7+ hours on {Math.round(metrics.efficiency)}% of recorded nights ) : ( @@ -463,4 +461,4 @@ const createThemedStyles = (colors: any) => }, }); -export default StatsScreen; \ No newline at end of file +export default StatsScreen; diff --git a/src/screens/TodayScreen.tsx b/src/screens/TodayScreen.tsx index a418713..8d56cb1 100644 --- a/src/screens/TodayScreen.tsx +++ b/src/screens/TodayScreen.tsx @@ -21,7 +21,6 @@ const SLEEP_TIPS = [ "Avoid large meals and beverages late at night", "Try relaxation techniques like deep breathing", "Limit daytime naps to 30 minutes or less", - "Try mouth taping to promote nasal breathing during sleep", "Consider using blackout curtains to block light", "Use sleep masks or earplugs to reduce light and noise", "If you can't sleep, get up and do something relaxing", @@ -36,7 +35,13 @@ const TodayScreen = () => { currentConfidence, todayRecords, getTodaySleepDuration, - manuallySetStatus + manuallySetStatus, + pendingSleepCandidate, + confirmSleepCandidate, + dismissSleepCandidate, + updateSleepCandidateBounds, + todayWellnessReport, + saveWellnessCheckIn, } = useSleep(); const { colors, isDarkMode } = useTheme(); const [dailyTip, setDailyTip] = useState(''); @@ -81,7 +86,9 @@ const TodayScreen = () => { // Get sleep quality assessment const getSleepQualityText = () => { - if (sleepDuration >= 480) { // 8+ hours + if (sleepDuration <= 0) { + return { text: 'No data yet', color: colors.textSecondary }; + } else if (sleepDuration >= 480) { // 8+ hours return { text: 'Excellent', color: '#4CAF50' }; } else if (sleepDuration >= 420) { // 7+ hours return { text: 'Good', color: '#8BC34A' }; @@ -117,6 +124,23 @@ const TodayScreen = () => { }; const sleepQuality = getSleepQualityText(); + + const savedLevel = (value?: number): 1 | 2 | 3 | 4 | 5 | undefined => + value === 1 || value === 2 || value === 3 || value === 4 || value === 5 + ? value + : undefined; + + const saveCheckInLevel = ( + kind: 'stress' | 'anxiety', + level: 1 | 2 | 3 | 4 | 5, + ) => saveWellnessCheckIn({ + stressLevel: kind === 'stress' + ? level + : savedLevel(todayWellnessReport?.selfReportedStress), + anxietyLevel: kind === 'anxiety' + ? level + : savedLevel(todayWellnessReport?.selfReportedAnxiety), + }); // Get color for confidence level const getConfidenceColor = (confidence: number) => { @@ -186,6 +210,79 @@ const TodayScreen = () => { + + {pendingSleepCandidate && ( + + + + Probable sleep detected + + + You were away from Sleep Detector for long enough to suggest sleep. Confirm this estimate before it is saved or sent to Apple Health. + + + {formatTime(pendingSleepCandidate.startTime)} – {formatTime(pendingSleepCandidate.endTime)} + + + {formatDuration((pendingSleepCandidate.endTime - pendingSleepCandidate.startTime) / 60_000)} + + + {pendingSleepCandidate.confidence}% confidence · {pendingSleepCandidate.explanation ?? 'Review the estimate before saving it.'} + + + + Sleep start + updateSleepCandidateBounds( + pendingSleepCandidate.startTime - 15 * 60_000, + pendingSleepCandidate.endTime, + )} + > + −15m + + updateSleepCandidateBounds( + pendingSleepCandidate.startTime + 15 * 60_000, + pendingSleepCandidate.endTime, + )} + > + +15m + + + + Wake time + updateSleepCandidateBounds( + pendingSleepCandidate.startTime, + pendingSleepCandidate.endTime - 15 * 60_000, + )} + > + −15m + + updateSleepCandidateBounds( + pendingSleepCandidate.startTime, + pendingSleepCandidate.endTime + 15 * 60_000, + )} + > + +15m + + + + + + Not sleep + + + Confirm sleep + + + + )} Today's Sleep @@ -194,7 +291,7 @@ const TodayScreen = () => { {formatDuration(sleepDuration)} - Quality: + Duration assessment: {sleepQuality.text} @@ -202,6 +299,96 @@ const TodayScreen = () => { + + {todayWellnessReport && ( + + + + Daily wellness report + + + + + {todayWellnessReport.sleepConsistencyScore}% + + Timing consistency + + + + {todayWellnessReport.sleepContinuityScore !== undefined + ? `${todayWellnessReport.sleepContinuityScore}%` + : '—'} + + Continuity + + + + {todayWellnessReport.recoverySignal === 'limited-data' + ? 'Learning' + : todayWellnessReport.recoverySignal.replace('-', ' ')} + + Recovery trend + + + + {(todayWellnessReport.heartRateVariabilityMs !== undefined || + todayWellnessReport.restingHeartRate !== undefined) && ( + + {todayWellnessReport.heartRateVariabilityMs !== undefined + ? `HRV ${Math.round(todayWellnessReport.heartRateVariabilityMs)} ms` + : ''} + {todayWellnessReport.heartRateVariabilityMs !== undefined && + todayWellnessReport.restingHeartRate !== undefined + ? ' · ' + : ''} + {todayWellnessReport.restingHeartRate !== undefined + ? `Resting HR ${Math.round(todayWellnessReport.restingHeartRate)} bpm` + : ''} + + )} + + {todayWellnessReport.observations.map(observation => ( + + • {observation} + + ))} + + Optional daily check-in + {(['stress', 'anxiety'] as const).map(kind => { + const selected = kind === 'stress' + ? todayWellnessReport.selfReportedStress + : todayWellnessReport.selfReportedAnxiety; + return ( + + + {kind === 'stress' ? 'Stress' : 'Anxiety'} + + {([1, 2, 3, 4, 5] as const).map(level => ( + saveCheckInLevel(kind, level)} + > + + {level} + + + ))} + + ); + })} + + Wellness trends only. Correlations do not show cause and are not a medical diagnosis. + + + )} Activity Timeline @@ -302,6 +489,102 @@ const createThemedStyles = (colors: any) => StyleSheet.create({ shadowRadius: 4, elevation: 2, }, + candidateCard: { + backgroundColor: colors.card, + borderRadius: 12, + borderWidth: 1, + borderColor: colors.primary, + marginHorizontal: 16, + marginBottom: 16, + padding: 16, + }, + candidateHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, + candidateDescription: { + color: colors.textSecondary, + lineHeight: 20, + marginBottom: 12, + }, + candidateTime: { + color: colors.text, + fontSize: 18, + fontWeight: '600', + textAlign: 'center', + }, + candidateDuration: { + color: colors.primary, + fontSize: 16, + fontWeight: '600', + marginTop: 4, + textAlign: 'center', + }, + candidateEvidence: { + color: colors.textSecondary, + fontSize: 13, + lineHeight: 18, + marginTop: 10, + }, + boundaryEditor: { + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.border, + marginTop: 12, + paddingTop: 8, + }, + boundaryRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginTop: 8, + }, + boundaryLabel: { + color: colors.text, + flex: 1, + fontWeight: '600', + }, + boundaryButton: { + borderColor: colors.border, + borderRadius: 8, + borderWidth: 1, + minWidth: 58, + paddingHorizontal: 10, + paddingVertical: 8, + }, + boundaryButtonText: { + color: colors.primary, + fontWeight: '700', + textAlign: 'center', + }, + candidateActions: { + flexDirection: 'row', + gap: 12, + marginTop: 16, + }, + dismissCandidateButton: { + flex: 1, + borderColor: colors.border, + borderRadius: 8, + borderWidth: 1, + paddingVertical: 12, + }, + dismissCandidateText: { + color: colors.text, + fontWeight: '600', + textAlign: 'center', + }, + confirmCandidateButton: { + flex: 1, + backgroundColor: colors.primary, + borderRadius: 8, + paddingVertical: 12, + }, + confirmCandidateText: { + color: '#fff', + fontWeight: '600', + textAlign: 'center', + }, statusHeader: { flexDirection: 'row', justifyContent: 'space-between', @@ -373,6 +656,89 @@ const createThemedStyles = (colors: any) => StyleSheet.create({ shadowRadius: 4, elevation: 2, }, + wellnessCard: { + backgroundColor: colors.card, + borderRadius: 12, + marginHorizontal: 16, + marginBottom: 16, + padding: 16, + }, + wellnessMetrics: { + flexDirection: 'row', + gap: 8, + marginVertical: 12, + }, + wellnessMetric: { + backgroundColor: colors.surface, + borderRadius: 8, + flex: 1, + minHeight: 70, + padding: 8, + }, + wellnessMetricValue: { + color: colors.primary, + fontSize: 15, + fontWeight: '700', + textTransform: 'capitalize', + }, + wellnessMetricLabel: { + color: colors.textSecondary, + fontSize: 11, + lineHeight: 14, + marginTop: 4, + }, + wellnessSignals: { + color: colors.text, + fontWeight: '600', + marginBottom: 8, + }, + wellnessObservation: { + color: colors.textSecondary, + lineHeight: 19, + marginTop: 4, + }, + checkInTitle: { + color: colors.text, + fontSize: 15, + fontWeight: '700', + marginTop: 16, + }, + checkInRow: { + alignItems: 'center', + flexDirection: 'row', + gap: 7, + marginTop: 10, + }, + checkInLabel: { + color: colors.text, + flex: 1, + }, + checkInButton: { + alignItems: 'center', + borderColor: colors.border, + borderRadius: 16, + borderWidth: 1, + height: 32, + justifyContent: 'center', + width: 32, + }, + checkInButtonSelected: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + checkInButtonText: { + color: colors.text, + fontWeight: '600', + }, + checkInButtonTextSelected: { + color: '#fff', + }, + wellnessDisclaimer: { + color: colors.textSecondary, + fontSize: 11, + lineHeight: 15, + marginTop: 14, + }, cardTitle: { fontSize: 18, fontWeight: '600', @@ -522,4 +888,4 @@ const createThemedStyles = (colors: any) => StyleSheet.create({ } }); -export default TodayScreen; \ No newline at end of file +export default TodayScreen; diff --git a/src/services/AppleHealthService.ts b/src/services/AppleHealthService.ts index 753ad0a..664c16c 100644 --- a/src/services/AppleHealthService.ts +++ b/src/services/AppleHealthService.ts @@ -1,166 +1,214 @@ import { Platform } from 'react-native'; +import { + AuthorizationStatus, + CategoryValueSleepAnalysis, + authorizationStatusFor, + isHealthDataAvailable, + queryCategorySamples, + queryQuantitySamples, + requestAuthorization, + saveCategorySample, +} from '@kingstinct/react-native-healthkit'; import { SleepEntry } from '../types/SleepEntry'; +import { HealthRecoverySnapshot } from '../types'; -let AppleHealthKit: any = null; -try { - AppleHealthKit = require('react-native-health').default; -} catch (error) { - console.warn('react-native-health not available:', error); -} - -// Define permission options for HealthKit -const permissions = { - permissions: { - read: [ - 'SleepAnalysis', - ], - write: [ - 'SleepAnalysis', - ], - }, -}; - -// Define the sleep sample interface -interface SleepSample { - id?: string; - startDate: string; - endDate: string; - value: number; - sourceId?: string; - sourceName?: string; -} +const SLEEP_TYPE = 'HKCategoryTypeIdentifierSleepAnalysis' as const; +const RESTING_HEART_RATE_TYPE = 'HKQuantityTypeIdentifierRestingHeartRate' as const; +const HRV_TYPE = 'HKQuantityTypeIdentifierHeartRateVariabilitySDNN' as const; +const RESPIRATORY_RATE_TYPE = 'HKQuantityTypeIdentifierRespiratoryRate' as const; +const WRIST_TEMPERATURE_TYPE = 'HKQuantityTypeIdentifierAppleSleepingWristTemperature' as const; -/** - * Service for interacting with Apple HealthKit - */ +/** Apple HealthKit adapter for reading and writing sleep analysis samples. */ export class AppleHealthService { - // Flag to track if we have permission - private hasPermission: boolean = false; - - /** - * Initialize the health service and request permissions - * @returns Promise that resolves when permissions are granted - */ - public initialize(): Promise { - // Only proceed on iOS - if (Platform.OS !== 'ios' || !AppleHealthKit) { - return Promise.resolve(); + private hasPermission = false; + + public async initialize(requestPermissions = false): Promise { + if (!this.isAvailable()) { + this.hasPermission = false; + return false; } - return new Promise((resolve, reject) => { - try { - // Check if initHealthKit method exists - if (typeof AppleHealthKit.initHealthKit !== 'function') { - console.warn('AppleHealthKit.initHealthKit is not available - health integration disabled'); - this.hasPermission = false; - resolve(); // Don't reject - just disable health integration - return; - } - - AppleHealthKit.initHealthKit(permissions, (error: string) => { - if (error) { - console.warn(`HealthKit initialization failed: ${error} - health integration disabled`); - this.hasPermission = false; - resolve(); // Don't reject - just disable health integration - return; - } - - this.hasPermission = true; - console.log('HealthKit initialized successfully'); - resolve(); + try { + if (requestPermissions) { + await requestAuthorization({ + toRead: [ + SLEEP_TYPE, + RESTING_HEART_RATE_TYPE, + HRV_TYPE, + RESPIRATORY_RATE_TYPE, + WRIST_TEMPERATURE_TYPE, + ], + toShare: [SLEEP_TYPE], }); - } catch (error) { - console.warn('Failed to initialize health service:', error, '- health integration disabled'); - this.hasPermission = false; - resolve(); // Don't reject - just disable health integration } - }); + this.hasPermission = authorizationStatusFor(SLEEP_TYPE) === AuthorizationStatus.sharingAuthorized; + return this.hasPermission; + } catch (error) { + this.hasPermission = false; + console.warn('HealthKit authorization failed:', error); + return false; + } } - /** - * Check if HealthKit is available on this device - * @returns Boolean indicating if HealthKit is available - */ public isAvailable(): boolean { - return Platform.OS === 'ios' && AppleHealthKit !== null; + return Platform.OS === 'ios' && isHealthDataAvailable(); } - /** - * Check if we have required permissions - * @returns Boolean indicating if we have permissions - */ public hasRequiredPermissions(): boolean { return this.hasPermission; } - /** - * Get sleep data from HealthKit for a specific date range - * @param startDate Beginning date to fetch from - * @param endDate End date to fetch to - * @returns Promise with array of sleep entries - */ public async getSleepData(startDate: Date, endDate: Date): Promise { - if (!this.isAvailable() || !this.hasPermission || !AppleHealthKit) { - return Promise.resolve([]); - } - - const options = { - startDate: startDate.toISOString(), - endDate: endDate.toISOString(), - }; + if (!this.isAvailable()) return []; + + const samples = await queryCategorySamples(SLEEP_TYPE, { + limit: 0, + ascending: true, + // Include samples that begin before the requested boundary but overlap + // it, then clip/merge them in the analysis layer. + filter: { date: { startDate, endDate, strictStartDate: false } }, + }); - return new Promise((resolve, reject) => { - try { - AppleHealthKit.getSleepSamples(options, (error: string, results: SleepSample[]) => { - if (error) { - reject(new Error(`Error getting sleep samples: ${error}`)); - return; - } - - // Convert HealthKit samples to our SleepEntry format - const sleepEntries: SleepEntry[] = results.map(sample => ({ - id: `health-${sample.id || sample.startDate}`, - startTime: new Date(sample.startDate).getTime(), - endTime: new Date(sample.endDate).getTime(), - source: sample.sourceName || 'Apple Health', - confidence: 95, // High confidence for HealthKit data - isAwake: sample.value === 0, // Convert HealthKit's sleep values to our format - })); - - resolve(sleepEntries); - }); - } catch (error) { - console.error('Failed to get sleep data:', error); - resolve([]); - } + return samples.flatMap(sample => { + const stage = this.sleepStage(sample.value); + if (!stage) return []; + return [{ + id: `health-${sample.uuid}`, + startTime: sample.startDate.getTime(), + endTime: sample.endDate.getTime(), + source: sample.sourceRevision.source.name || 'Apple Health', + confidence: 95, + isAwake: stage === 'awake', + stage, + }]; }); } - /** - * Save sleep data to HealthKit - * This is a mock implementation since direct writing to HealthKit requires - * additional native code setup that may not be available in the current app build - * @param sleepEntry The sleep entry to save - * @returns Promise that resolves when saved - */ + public async getRecoverySnapshot(startDate: Date, endDate: Date): Promise { + if (!this.isAvailable()) return { startTime: startDate.getTime(), endTime: endDate.getTime() }; + + const filter = { date: { startDate, endDate, strictStartDate: true } }; + const [restingHeartRate, hrv, respiratoryRate, wristTemperature] = await Promise.all([ + queryQuantitySamples(RESTING_HEART_RATE_TYPE, { + limit: 0, + ascending: true, + unit: 'count/min', + filter, + }), + queryQuantitySamples(HRV_TYPE, { + limit: 0, + ascending: true, + unit: 'ms', + filter, + }), + queryQuantitySamples(RESPIRATORY_RATE_TYPE, { + limit: 0, + ascending: true, + unit: 'count/min', + filter, + }), + queryQuantitySamples(WRIST_TEMPERATURE_TYPE, { + limit: 0, + ascending: true, + unit: 'degC', + filter, + }), + ]); + + return { + startTime: startDate.getTime(), + endTime: endDate.getTime(), + restingHeartRate: this.average(restingHeartRate.map(sample => sample.quantity)), + heartRateVariabilityMs: this.average(hrv.map(sample => sample.quantity)), + respiratoryRate: this.average(respiratoryRate.map(sample => sample.quantity)), + wristTemperatureCelsius: this.average(wristTemperature.map(sample => sample.quantity)), + }; + } + public async saveSleepData(sleepEntry: SleepEntry): Promise { - if (!this.isAvailable() || !this.hasPermission) { - return Promise.resolve(); + if (!this.hasPermission) { + throw new Error('Apple Health sleep write permission has not been granted.'); + } + if (sleepEntry.isAwake || sleepEntry.endTime <= sleepEntry.startTime) return; + + const existing = await queryCategorySamples(SLEEP_TYPE, { + limit: 0, + filter: { + date: { + startDate: new Date(sleepEntry.startTime), + endDate: new Date(sleepEntry.endTime), + }, + }, + }); + const alreadySynced = existing.some(sample => { + const syncIdentifier = sample.metadata?.HKSyncIdentifier; + const isSleepDetectorSample = typeof syncIdentifier === 'string' + && syncIdentifier.startsWith('detected-'); + const coversEntry = sample.startDate.getTime() <= sleepEntry.startTime + && sample.endDate.getTime() >= sleepEntry.endTime; + + return syncIdentifier === sleepEntry.id || (isSleepDetectorSample && coversEntry); + }); + if (alreadySynced) return; + + const asleepIntervals = existing.flatMap(sample => { + const stage = this.sleepStage(sample.value); + if (!stage || stage === 'awake') return []; + const start = Math.max(sleepEntry.startTime, sample.startDate.getTime()); + const end = Math.min(sleepEntry.endTime, sample.endDate.getTime()); + return end > start ? [{ start, end }] : []; + }).sort((left, right) => left.start - right.start); + let coveredMilliseconds = 0; + let coveredUntil = sleepEntry.startTime; + for (const interval of asleepIntervals) { + const uncoveredStart = Math.max(coveredUntil, interval.start); + if (interval.end > uncoveredStart) { + coveredMilliseconds += interval.end - uncoveredStart; + coveredUntil = interval.end; + } + } + if (coveredMilliseconds / (sleepEntry.endTime - sleepEntry.startTime) >= 0.9) { + // Another Health source already represents this night. Avoid writing a + // duplicate sample that would inflate totals in downstream apps. + return; } - // TODO: Write to HealthKit - // startDate: new Date(sleepEntry.startTime).toISOString(), - //endDate: new Date(sleepEntry.endTime).toISOString(), - //value: sleepEntry.isAwake ? 0 : 1, // 0 is awake, 1 is asleep in HealthKit - + await saveCategorySample( + SLEEP_TYPE, + CategoryValueSleepAnalysis.asleepUnspecified, + new Date(sleepEntry.startTime), + new Date(sleepEntry.endTime), + { + HKWasUserEntered: true, + HKTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + HKExternalUUID: sleepEntry.id, + HKSyncIdentifier: sleepEntry.id, + HKSyncVersion: 1, + } as any, + ); + } - // Mock successful write - return Promise.resolve(); + private average(values: readonly number[]): number | undefined { + const finite = values.filter(Number.isFinite); + if (finite.length === 0) return undefined; + return finite.reduce((sum, value) => sum + value, 0) / finite.length; + } - // Note: To actually implement this when ready, you'll need to: - // 1. Add additional native code to support writing to HealthKit - // 2. Update react-native-health to the latest version - // 3. Use the appropriate method, which might be named differently - // depending on the library version + private sleepStage(value: CategoryValueSleepAnalysis): SleepEntry['stage'] | null { + switch (value) { + case CategoryValueSleepAnalysis.awake: + return 'awake'; + case CategoryValueSleepAnalysis.asleepCore: + return 'core'; + case CategoryValueSleepAnalysis.asleepDeep: + return 'deep'; + case CategoryValueSleepAnalysis.asleepREM: + return 'rem'; + case CategoryValueSleepAnalysis.asleepUnspecified: + return 'unspecified'; + default: + // `inBed` is not proof of sleep and must not be counted as asleep. + return null; + } } -} \ No newline at end of file +} diff --git a/src/services/BackgroundActivityService.ts b/src/services/BackgroundActivityService.ts index 528f324..78076f8 100644 --- a/src/services/BackgroundActivityService.ts +++ b/src/services/BackgroundActivityService.ts @@ -1,55 +1,10 @@ -import * as BackgroundFetch from 'expo-background-fetch'; -import * as TaskManager from 'expo-task-manager'; -import { Accelerometer, Gyroscope } from 'expo-sensors'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { AppState, AppStateStatus } from 'react-native'; -import { NotificationService } from './NotificationService'; +import { AppStateStatus } from 'react-native'; -// Background task name -const BACKGROUND_ACTIVITY_TASK = 'background-activity-monitor'; - -// Storage keys const LAST_ACTIVITY_KEY = 'last-device-activity'; +const INACTIVE_SINCE_KEY = 'sleep-detector-inactive-since'; const ACTIVITY_LOG_KEY = 'device-activity-log'; -const SETTINGS_KEY = 'sleep-tracker-settings'; // Same key as SleepContext - -// Activity detection thresholds - now configurable based on sensitivity -const SENSITIVITY_SETTINGS = { - low: { - MOVEMENT_THRESHOLD: 0.06, // Enhanced from 0.08 for better detection - GYRO_THRESHOLD: 0.04, // Enhanced from 0.05 - ACTIVITY_CHECK_INTERVAL: 25000, // More frequent than 30s - BACKGROUND_CHECK_INTERVAL: 45000, // More frequent than 60s - NOISE_REDUCTION_FACTOR: 0.7, // Less noise filtering for conservative detection - }, - medium: { - MOVEMENT_THRESHOLD: 0.04, // Enhanced from 0.05 for optimal balance - GYRO_THRESHOLD: 0.025, // Enhanced from 0.03 - ACTIVITY_CHECK_INTERVAL: 15000, // More frequent than 20s - BACKGROUND_CHECK_INTERVAL: 25000, // More frequent than 30s - NOISE_REDUCTION_FACTOR: 0.8, // Balanced noise filtering - }, - high: { - MOVEMENT_THRESHOLD: 0.025, // Enhanced from 0.03 for maximum sensitivity - GYRO_THRESHOLD: 0.015, // Enhanced from 0.02 - ACTIVITY_CHECK_INTERVAL: 12000, // More frequent than 15s - BACKGROUND_CHECK_INTERVAL: 18000, // More frequent than 20s - NOISE_REDUCTION_FACTOR: 0.9, // Higher noise filtering to prevent false positives - } -}; - -const INACTIVITY_UPDATE_INTERVAL = 45000; // Update inactivity every 45 seconds (more frequent) - -// Default settings (should match SleepContext) -const DEFAULT_SETTINGS = { - inactivityThreshold: 30, // Reduced to 30 minutes for more responsive detection - useMachineLearning: true, - considerTimeOfDay: true, - sensitivityLevel: 'medium' as const, - adaptiveThreshold: true, - napDetection: true, - backgroundPersistence: 'aggressive' as 'normal' | 'aggressive' | 'maximum', -}; +const SETTINGS_KEY = 'sleep-tracker-settings'; interface ActivityData { timestamp: number; @@ -58,18 +13,23 @@ interface ActivityData { confidence: number; } +export interface InactivePeriod { + startTime: number; + endTime: number; + durationMinutes: number; +} + +/** + * Persists the app lifecycle boundary used by automatic sleep detection. + * + * iOS suspends JavaScript shortly after an app is backgrounded, so timers and + * motion listeners cannot be used as a reliable clock. Persisting the boundary + * lets us calculate the complete inactive interval when the app is used again, + * including after process termination or a device restart. + */ export class BackgroundActivityService { private static instance: BackgroundActivityService; private isMonitoring = false; - private lastMovementTimestamp = Date.now(); - private lastActivityUpdate = Date.now(); // Track when we last updated activity - private accelerometerSubscription: any = null; - private gyroscopeSubscription: any = null; - private activityCheckInterval: NodeJS.Timeout | null = null; - private inactivityUpdateInterval: NodeJS.Timeout | null = null; // New interval for inactivity updates - private appStateSubscription: any = null; - private currentAppState: AppStateStatus = 'active'; - private currentSensitivity: 'low' | 'medium' | 'high' = 'medium'; // Track current sensitivity level public static getInstance(): BackgroundActivityService { if (!BackgroundActivityService.instance) { @@ -78,767 +38,143 @@ export class BackgroundActivityService { return BackgroundActivityService.instance; } - /** - * Initialize the background activity monitoring service - */ public async initialize(): Promise { - try { - // Register background task - await this.registerBackgroundTask(); - - // Set up sensor monitoring - await this.setupSensorMonitoring(); - - // Set up app state monitoring - this.setupAppStateMonitoring(); - - // Start background fetch - await this.startBackgroundFetch(); - - console.log('BackgroundActivityService initialized successfully'); - } catch (error) { - console.error('Failed to initialize BackgroundActivityService:', error); - } - } - - /** - * Get user settings from storage and update sensitivity - */ - private async getUserSettings(): Promise { - try { - const settings = await AsyncStorage.getItem(SETTINGS_KEY); - if (settings) { - const parsedSettings = { ...DEFAULT_SETTINGS, ...JSON.parse(settings) }; - this.currentSensitivity = parsedSettings.sensitivityLevel || 'medium'; - return parsedSettings; - } - } catch (error) { - console.error('Failed to get user settings:', error); - } - return DEFAULT_SETTINGS; - } - - /** - * Get current sensitivity thresholds - */ - private getSensitivityThresholds() { - return SENSITIVITY_SETTINGS[this.currentSensitivity]; - } - - /** - * Register the background task for activity monitoring - */ - private async registerBackgroundTask(): Promise { - TaskManager.defineTask(BACKGROUND_ACTIVITY_TASK, async () => { - try { - const now = Date.now(); - const lastActivity = await this.getLastActivityTimestamp(); - const inactiveTime = (now - lastActivity) / (1000 * 60); // minutes - - // Get user settings - const userSettings = await this.getUserSettings(); - - // Log detailed activity check - await this.logActivity({ - timestamp: now, - hasMovement: false, - appState: 'background', - confidence: 90 - }); - - // CRITICAL FIX: Precise threshold detection - if (inactiveTime >= userSettings.inactivityThreshold) { - // Check if we've already recorded this threshold crossing - const lastThresholdEventStr = await AsyncStorage.getItem('SLEEP_THRESHOLD_CROSSED'); - let shouldTriggerEvent = true; - - if (lastThresholdEventStr) { - const lastEvent = JSON.parse(lastThresholdEventStr); - // Don't trigger again if we've already triggered within the last 5 minutes - const timeSinceLastEvent = (now - lastEvent.timestamp) / (1000 * 60); - if (lastEvent.detected && timeSinceLastEvent < 5) { - shouldTriggerEvent = false; - console.log(`⏭️ Skipping duplicate threshold event (${Math.round(timeSinceLastEvent)}min ago)`); - } - } - - if (shouldTriggerEvent) { - // Calculate when sleep actually started (at the threshold moment) - const sleepStartTime = now - (userSettings.inactivityThreshold * 60 * 1000); - - // Store threshold crossing event for SleepContext to process immediately - const thresholdEvent = { - detected: true, - timestamp: now, - detectionTime: now, - sleepStartTime: sleepStartTime, - inactiveMinutes: inactiveTime, - thresholdMinutes: userSettings.inactivityThreshold, - notificationSent: false - }; - - await AsyncStorage.setItem('SLEEP_THRESHOLD_CROSSED', JSON.stringify(thresholdEvent)); - - // Send immediate notification about sleep detection - try { - await this.notifyPotentialSleep(inactiveTime); - // Update the event to mark notification as sent - thresholdEvent.notificationSent = true; - await AsyncStorage.setItem('SLEEP_THRESHOLD_CROSSED', JSON.stringify(thresholdEvent)); - } catch (error) { - console.error('Failed to send sleep detection notification:', error); - } - } - } else { - // Still active or not enough inactivity - clear any stale threshold events - const existingEvent = await AsyncStorage.getItem('SLEEP_THRESHOLD_CROSSED'); - if (existingEvent) { - await AsyncStorage.removeItem('SLEEP_THRESHOLD_CROSSED'); - } - } - - return BackgroundFetch.BackgroundFetchResult.NewData; - } catch (error) { - console.error('❌ Background task error:', error); - return BackgroundFetch.BackgroundFetchResult.Failed; - } - }); - } - - /** - * Set up sensor monitoring for movement detection - */ - private async setupSensorMonitoring(): Promise { - try { - // Get current sensitivity settings - const thresholds = this.getSensitivityThresholds(); - - // Configure accelerometer with enhanced settings - Accelerometer.setUpdateInterval(300); // Increased from 500ms for better responsiveness - - let lastSignificantMovement = 0; - let movementBuffer: number[] = []; - let previousMagnitude = 0; - - this.accelerometerSubscription = Accelerometer.addListener(accelerometerData => { - const { x, y, z } = accelerometerData; - const magnitude = Math.sqrt(x * x + y * y + z * z); - - // Enhanced noise filtering with derivative-based detection - const magnitudeDelta = Math.abs(magnitude - previousMagnitude); - previousMagnitude = magnitude; - - // Use a longer buffer for better noise reduction - movementBuffer.push(magnitude); - if (movementBuffer.length > 5) { // Increased from 3 for better filtering - movementBuffer.shift(); - } - - // Calculate weighted average with recent bias - const weights = [0.1, 0.15, 0.2, 0.25, 0.3]; // More weight to recent readings - const weightedMagnitude = movementBuffer - .map((val, idx) => val * (weights[idx] || 0.2)) - .reduce((sum, val) => sum + val, 0); - - // Multi-criteria movement detection - const avgMagnitude = movementBuffer.reduce((sum, val) => sum + val, 0) / movementBuffer.length; - const isSignificantMovement = ( - (avgMagnitude > thresholds.MOVEMENT_THRESHOLD && movementBuffer.length >= 4) || - (magnitudeDelta > thresholds.MOVEMENT_THRESHOLD * 1.5) || // Sudden movement - (weightedMagnitude > thresholds.MOVEMENT_THRESHOLD * thresholds.NOISE_REDUCTION_FACTOR) - ); - - if (isSignificantMovement) { - const now = Date.now(); - // Enhanced debouncing with adaptive timing - const debounceTime = this.currentSensitivity === 'high' ? 1500 : - this.currentSensitivity === 'medium' ? 2000 : 2500; - - if (now - lastSignificantMovement > debounceTime) { - lastSignificantMovement = now; - this.onMovementDetected('accelerometer'); - } - } - }); - - // Configure gyroscope with enhanced rotation detection - Gyroscope.setUpdateInterval(400); // Optimized from 500ms - - let lastSignificantRotation = 0; - let rotationBuffer: number[] = []; - let rotationVariance = 0; - - this.gyroscopeSubscription = Gyroscope.addListener(gyroscopeData => { - const { x, y, z } = gyroscopeData; - const magnitude = Math.sqrt(x * x + y * y + z * z); - - // Enhanced rotation detection with variance analysis - rotationBuffer.push(magnitude); - if (rotationBuffer.length > 4) { // Increased buffer for better analysis - rotationBuffer.shift(); - } - - if (rotationBuffer.length >= 4) { - const avgMagnitude = rotationBuffer.reduce((sum, val) => sum + val, 0) / rotationBuffer.length; - // Calculate variance to detect consistent rotation patterns - rotationVariance = rotationBuffer.reduce((sum, val) => sum + Math.pow(val - avgMagnitude, 2), 0) / rotationBuffer.length; - - // Multi-criteria rotation detection - const significantRotation = ( - avgMagnitude > thresholds.GYRO_THRESHOLD || - rotationVariance > thresholds.GYRO_THRESHOLD * 0.5 // Consistent rotation pattern - ); - - if (significantRotation) { - const now = Date.now(); - const debounceTime = this.currentSensitivity === 'high' ? 1200 : 1500; - - if (now - lastSignificantRotation > debounceTime) { - lastSignificantRotation = now; - this.onMovementDetected('gyroscope'); - } - } - } - }); - } catch (error) { - console.error('❌ Failed to set up enhanced sensor monitoring:', error); + this.isMonitoring = true; + const lastActivity = await AsyncStorage.getItem(LAST_ACTIVITY_KEY); + if (!lastActivity) { + await AsyncStorage.setItem(LAST_ACTIVITY_KEY, Date.now().toString()); } } - /** - * Set up app state monitoring - */ - private setupAppStateMonitoring(): void { - this.appStateSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => { - const previousState = this.currentAppState; - this.currentAppState = nextAppState; - - console.log(`📱 App state changed from ${previousState} to ${nextAppState}`); - - // If app becomes active, record as movement but be conservative about timing - if (nextAppState === 'active' && previousState !== 'active') { - // Start a grace period to see if user actually interacts with the app - this.startAppActiveGracePeriod(); - } - - // If app goes to background, start more aggressive monitoring - if (nextAppState === 'background' || nextAppState === 'inactive') { - this.onAppBackground(); - console.log('🌙 App went to background - starting background monitoring'); - } - }); - } - - /** - * Start a grace period when app becomes active to determine if it's genuine usage - */ - private startAppActiveGracePeriod(): void { - // Initially mark as just a check - this.onMovementDetected('app_active_check'); - - // Set up a timer to upgrade to genuine activity if user stays active - setTimeout(() => { - if (this.currentAppState === 'active') { - // User has been actively using the app for 10 seconds - this.onMovementDetected('app_active'); - - // Set up another timer for extended genuine usage - setTimeout(() => { - if (this.currentAppState === 'active') { - // User has been using app for 30 seconds - definitely genuine activity - this.onMovementDetected('user_interaction'); - } - }, 20000); // Additional 20 seconds = 30 total - } - }, 10000); // Reduced to 10 seconds for more responsive detection + public async startMonitoring(): Promise { + await this.initialize(); } - /** - * Start background fetch service - */ - private async startBackgroundFetch(): Promise { - try { - // Unregister any existing task first - try { - await BackgroundFetch.unregisterTaskAsync(BACKGROUND_ACTIVITY_TASK); - } catch (error) { - // Ignore errors during cleanup - } - - // Get user settings for adaptive intervals - const userSettings = await this.getUserSettings(); - let minimumInterval = 15; // Default 15 seconds - - // Adaptive interval based on persistence setting - switch (userSettings.backgroundPersistence) { - case 'maximum': - minimumInterval = 10; // Most aggressive - break; - case 'aggressive': - minimumInterval = 12; // Balanced aggressive - break; - case 'normal': - minimumInterval = 20; // Conservative - break; - } - - await BackgroundFetch.registerTaskAsync(BACKGROUND_ACTIVITY_TASK, { - minimumInterval, // Adaptive interval based on user preference - stopOnTerminate: false, - startOnBoot: true, - }); - } catch (error) { - console.error('Failed to register background fetch:', error); - } + public async stopMonitoring(): Promise { + this.isMonitoring = false; } - /** - * Handle detected movement - */ - private onMovementDetected(source: string = 'sensor'): void { - const now = Date.now(); - - // Only update last movement for actual physical movement, not just app activation - if (source !== 'app_active_check') { - this.lastMovementTimestamp = now; - this.lastActivityUpdate = now; - - // Store last activity timestamp - AsyncStorage.setItem(LAST_ACTIVITY_KEY, now.toString()); - } - - // Log the activity with proper confidence scoring - this.logActivity({ - timestamp: now, - hasMovement: source !== 'app_active_check', - appState: this.currentAppState, - confidence: this.getSourceConfidence(source) - }); + public emergencyShutdown(): void { + this.isMonitoring = false; } - /** - * Get confidence level based on activity source - */ - private getSourceConfidence(source: string): number { - switch (source) { - case 'user_interaction': return 100; // Manual user activity recording - case 'accelerometer': return 90; // Physical movement - case 'gyroscope': return 85; // Phone rotation/usage - case 'app_active': return 80; // App became active with likely usage - case 'app_active_check': return 40; // Just checking the app - default: return 70; // Generic sensor activity - } + public isActive(): boolean { + return this.isMonitoring; } - /** - * Handle app going to background - */ - private onAppBackground(): void { - // Get current sensitivity settings - const thresholds = this.getSensitivityThresholds(); - - // Clear any existing intervals - if (this.activityCheckInterval) { - clearInterval(this.activityCheckInterval); - } - if (this.inactivityUpdateInterval) { - clearInterval(this.inactivityUpdateInterval); - } - - // Set up periodic checks while in background - this.activityCheckInterval = setInterval(() => { - const now = Date.now(); - const timeSinceLastMovement = now - this.lastMovementTimestamp; - const timeSinceLastUpdate = now - this.lastActivityUpdate; - - // Log inactivity every check - this.logActivity({ - timestamp: now, + /** Preserve the first inactive timestamp across inactive -> background events. */ + public async recordAppInactive(timestamp = Date.now()): Promise { + const existing = await AsyncStorage.getItem(INACTIVE_SINCE_KEY); + if (!existing) { + await AsyncStorage.setItem(INACTIVE_SINCE_KEY, timestamp.toString()); + await this.logActivity({ + timestamp, hasMovement: false, appState: 'background', - confidence: 70 + confidence: 100, }); - }, thresholds.BACKGROUND_CHECK_INTERVAL); - - // Set up regular inactivity updates - this.inactivityUpdateInterval = setInterval(async () => { - const now = Date.now(); - const inactiveMinutes = (now - this.lastMovementTimestamp) / (1000 * 60); - - // Update the last activity timestamp in storage to current time if no movement - // This ensures getInactivityDuration() returns the correct value - if (inactiveMinutes > 1) { // Only if we've been inactive for more than 1 minute - console.log(`Updating inactivity: ${Math.round(inactiveMinutes)} minutes inactive`); - } - - this.lastActivityUpdate = now; - }, INACTIVITY_UPDATE_INTERVAL); - } - - /** - * Log activity data - */ - private async logActivity(activity: ActivityData): Promise { - try { - const existingLog = await AsyncStorage.getItem(ACTIVITY_LOG_KEY); - const log: ActivityData[] = existingLog ? JSON.parse(existingLog) : []; - - // Add new activity - log.push(activity); - - // Keep only last 1000 entries to manage storage - const recentLog = log.slice(-1000); - - await AsyncStorage.setItem(ACTIVITY_LOG_KEY, JSON.stringify(recentLog)); - } catch (error) { - console.error('Failed to log activity:', error); - } - } - - /** - * Record genuine user activity (for when we know the user is actually using the phone) - */ - public recordUserActivity(): void { - this.onMovementDetected('user_interaction'); - } - - /** - * Get the timestamp of the last detected activity - */ - public async getLastActivityTimestamp(): Promise { - try { - const timestamp = await AsyncStorage.getItem(LAST_ACTIVITY_KEY); - return timestamp ? parseInt(timestamp) : Date.now(); - } catch (error) { - console.error('Failed to get last activity timestamp:', error); - return Date.now(); } } /** - * Get recent activity log + * Consumes the pending inactive interval before recording resume. Reading + * this interval before updating LAST_ACTIVITY_KEY fixes the former resume + * bug where inactivity was always observed as approximately zero. */ - public async getActivityLog(hours: number = 24): Promise { - try { - const log = await AsyncStorage.getItem(ACTIVITY_LOG_KEY); - if (!log) return []; - - const activities: ActivityData[] = JSON.parse(log); - const cutoffTime = Date.now() - (hours * 60 * 60 * 1000); - - return activities.filter(activity => activity.timestamp >= cutoffTime); - } catch (error) { - console.error('Failed to get activity log:', error); - return []; - } - } + public async consumeInactivePeriod(resumedAt = Date.now()): Promise { + const stored = await AsyncStorage.getItem(INACTIVE_SINCE_KEY); + const startTime = stored === null ? Number.NaN : Number(stored); + const period = Number.isFinite(startTime) && + Number.isFinite(resumedAt) && + resumedAt > startTime + ? { + startTime, + endTime: resumedAt, + durationMinutes: (resumedAt - startTime) / 60_000, + } + : null; - /** - * Notify about potential sleep state - */ - private async notifyPotentialSleep(inactiveMinutes: number): Promise { - // Store the threshold crossing event for immediate pickup by sleep context + // Storage cleanup must never prevent the already-calculated boundary from + // reaching the detector. A failed removal can replay the same interval on + // the next launch, which is safe because candidates and sessions are + // de-duplicated; dropping the interval would lose an entire night. try { - await AsyncStorage.setItem('SLEEP_THRESHOLD_CROSSED', JSON.stringify({ - timestamp: Date.now(), - inactiveMinutes: inactiveMinutes, - detected: true - })); + await AsyncStorage.removeItem(INACTIVE_SINCE_KEY); } catch (error) { - console.error('Failed to store threshold crossing event:', error); + console.warn('Unable to clear the consumed inactivity boundary:', error); } - - // Send notification if user has been inactive long enough try { - const notificationService = NotificationService.getInstance(); - await notificationService.notifySleepDetected(inactiveMinutes); + await this.recordUserActivity(resumedAt); } catch (error) { - console.error('Failed to send sleep detection notification from background:', error); - } - } - - /** - * Check if device has been inactive for specified duration - * @param includeCurrentCheck - if false, returns time since last genuine activity (not just app checks) - */ - public async getInactivityDuration(includeCurrentCheck: boolean = false): Promise { - const lastActivity = await this.getLastActivityTimestamp(); - const now = Date.now(); - const inactiveMinutes = (now - lastActivity) / (1000 * 60); - - // If we're currently checking the app but want to ignore the current session - if (!includeCurrentCheck && this.currentAppState === 'active') { - // Get the activity log to find the last non-check activity - const recentActivity = await this.getActivityLog(3); // Extended to 3 hours for better analysis - - // Find the last activity that was genuine movement (not just app checks) - const lastGenuineActivity = recentActivity - .filter(activity => activity.hasMovement === true && activity.confidence >= 70) - .sort((a, b) => b.timestamp - a.timestamp)[0]; - - if (lastGenuineActivity) { - const genuineInactiveMinutes = (now - lastGenuineActivity.timestamp) / (1000 * 60); - console.log(`📊 Inactivity Analysis: ${Math.round(inactiveMinutes)}min total, ${Math.round(genuineInactiveMinutes)}min genuine inactivity`); - return Math.max(0, genuineInactiveMinutes); - } else { - // If no genuine activity found in recent history, analyze recent app state changes - const recentAppActivity = recentActivity - .filter(activity => activity.timestamp > (now - (2 * 60 * 60 * 1000))) // Last 2 hours - .sort((a, b) => b.timestamp - a.timestamp); - - if (recentAppActivity.length > 0) { - // Find the most recent high-confidence activity - const lastHighConfidenceActivity = recentAppActivity.find(activity => activity.confidence >= 85); - if (lastHighConfidenceActivity) { - const adjustedInactiveMinutes = (now - lastHighConfidenceActivity.timestamp) / (1000 * 60); - console.log(`📊 Using high-confidence activity timestamp: ${Math.round(adjustedInactiveMinutes)}min inactivity`); - return Math.max(0, adjustedInactiveMinutes); - } - } - - // Fall back to buffered approach but with more conservative buffer - const bufferedInactiveMinutes = Math.max(0, inactiveMinutes - 5); // 5 minute buffer for app session - console.log(`📊 No recent genuine activity found, using conservative buffered inactivity: ${Math.round(bufferedInactiveMinutes)}min`); - return bufferedInactiveMinutes; - } + console.warn('Unable to persist the app resume boundary:', error); } - // Log the inactivity check for debugging - console.log(`📊 Total inactivity: ${Math.round(inactiveMinutes)} minutes since last activity`); - - return Math.max(0, inactiveMinutes); // Ensure non-negative return + return period; } - /** - * Start monitoring - */ - public async startMonitoring(): Promise { - if (this.isMonitoring) { - console.log('Background monitoring already active'); - return; - } - - this.isMonitoring = true; - this.lastMovementTimestamp = Date.now(); - this.lastActivityUpdate = Date.now(); - - // Initialize the last activity timestamp - await AsyncStorage.setItem(LAST_ACTIVITY_KEY, Date.now().toString()); - - await this.initialize(); - console.log('Background activity monitoring started successfully'); + public async recordUserActivity(timestamp = Date.now()): Promise { + await AsyncStorage.setItem(LAST_ACTIVITY_KEY, timestamp.toString()); + await this.logActivity({ + timestamp, + hasMovement: true, + appState: 'active', + confidence: 100, + }); } - /** - * Stop monitoring with enhanced error handling - */ - public async stopMonitoring(): Promise { - if (!this.isMonitoring) { - console.log('📱 Background monitoring already stopped'); - return; - } - - console.log('🛑 Stopping background activity monitoring...'); - this.isMonitoring = false; - - // Enhanced cleanup with individual try-catch blocks to prevent cascade failures - - // Clean up accelerometer subscription - try { - if (this.accelerometerSubscription) { - this.accelerometerSubscription.remove(); - this.accelerometerSubscription = null; - console.log('✅ Accelerometer subscription cleaned up'); - } - } catch (error) { - console.error('❌ Error cleaning up accelerometer:', error); - } - - // Clean up gyroscope subscription - try { - if (this.gyroscopeSubscription) { - this.gyroscopeSubscription.remove(); - this.gyroscopeSubscription = null; - console.log('✅ Gyroscope subscription cleaned up'); - } - } catch (error) { - console.error('❌ Error cleaning up gyroscope:', error); - } - - // Clean up app state subscription - try { - if (this.appStateSubscription) { - this.appStateSubscription.remove(); - this.appStateSubscription = null; - console.log('✅ App state subscription cleaned up'); - } - } catch (error) { - console.error('❌ Error cleaning up app state subscription:', error); - } - - // Clear intervals - try { - if (this.activityCheckInterval) { - clearInterval(this.activityCheckInterval); - this.activityCheckInterval = null; - console.log('✅ Activity check interval cleared'); - } - } catch (error) { - console.error('❌ Error clearing activity check interval:', error); - } - - try { - if (this.inactivityUpdateInterval) { - clearInterval(this.inactivityUpdateInterval); - this.inactivityUpdateInterval = null; - console.log('✅ Inactivity update interval cleared'); - } - } catch (error) { - console.error('❌ Error clearing inactivity update interval:', error); - } - - // Unregister background task with timeout protection - try { - console.log('🔄 Unregistering background task...'); - - // Add timeout to prevent hanging - const unregisterPromise = BackgroundFetch.unregisterTaskAsync(BACKGROUND_ACTIVITY_TASK); - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Background task unregister timeout')), 5000) - ); - - await Promise.race([unregisterPromise, timeoutPromise]); - console.log('✅ Background task unregistered successfully'); - } catch (error) { - console.error('❌ Failed to unregister background task (non-critical):', error); - // Don't throw here - this is cleanup, not critical for app function - } + public async getLastActivityTimestamp(): Promise { + const inactiveSince = await AsyncStorage.getItem(INACTIVE_SINCE_KEY); + if (inactiveSince && Number.isFinite(Number(inactiveSince))) return Number(inactiveSince); - console.log('🎯 Background activity monitoring stopped successfully'); + const timestamp = await AsyncStorage.getItem(LAST_ACTIVITY_KEY); + return timestamp && Number.isFinite(Number(timestamp)) ? Number(timestamp) : Date.now(); } - /** - * Get monitoring status - */ - public isActive(): boolean { - return this.isMonitoring; + public async getInactivityDuration(_includeCurrentCheck = false): Promise { + const lastActivity = await this.getLastActivityTimestamp(); + return Math.max(0, (Date.now() - lastActivity) / 60_000); } - /** - * Emergency shutdown - force cleanup without waiting for async operations - * Use this when the app is being force-closed - */ - public emergencyShutdown(): void { - console.log('🚨 Emergency shutdown initiated'); - + public async getActivityLog(hours = 24): Promise { try { - this.isMonitoring = false; - - // Synchronous cleanup only - if (this.activityCheckInterval) { - clearInterval(this.activityCheckInterval); - this.activityCheckInterval = null; - } - - if (this.inactivityUpdateInterval) { - clearInterval(this.inactivityUpdateInterval); - this.inactivityUpdateInterval = null; - } - - // Note: We skip sensor cleanup here as it might be async and cause crashes - console.log('🎯 Emergency shutdown completed'); - } catch (error) { - console.error('❌ Error during emergency shutdown:', error); - // Don't rethrow - we're already in an emergency situation + const raw = await AsyncStorage.getItem(ACTIVITY_LOG_KEY); + const log: ActivityData[] = raw ? JSON.parse(raw) : []; + const cutoff = Date.now() - hours * 60 * 60 * 1000; + return log.filter(item => item.timestamp >= cutoff); + } catch { + return []; } } - /** - * Force check for threshold crossing (can be called manually for immediate checking) - */ public async forceThresholdCheck(): Promise { - try { - const now = Date.now(); - const inactiveTime = await this.getInactivityDuration(false); - const userSettings = await this.getUserSettings(); - - console.log(`🔍 FORCE THRESHOLD CHECK: ${Math.round(inactiveTime)}min inactive (threshold: ${userSettings.inactivityThreshold}min)`); - - if (inactiveTime >= userSettings.inactivityThreshold) { - // Check if we've already recorded this threshold crossing recently - const lastThresholdEventStr = await AsyncStorage.getItem('SLEEP_THRESHOLD_CROSSED'); - let shouldTriggerEvent = true; - - if (lastThresholdEventStr) { - const lastEvent = JSON.parse(lastThresholdEventStr); - const timeSinceLastEvent = (now - lastEvent.timestamp) / (1000 * 60); - if (lastEvent.detected && timeSinceLastEvent < 3) { - shouldTriggerEvent = false; - console.log(`⏭️ Skipping duplicate force threshold check (${Math.round(timeSinceLastEvent)}min ago)`); - } - } - - if (shouldTriggerEvent) { - console.log(`🚨 FORCE THRESHOLD TRIGGERED! ${Math.round(inactiveTime)} minutes inactive`); - - const sleepStartTime = now - (userSettings.inactivityThreshold * 60 * 1000); - - const thresholdEvent = { - detected: true, - timestamp: now, - detectionTime: now, - sleepStartTime: sleepStartTime, - inactiveMinutes: inactiveTime, - thresholdMinutes: userSettings.inactivityThreshold, - notificationSent: false, - source: 'force_check' - }; - - await AsyncStorage.setItem('SLEEP_THRESHOLD_CROSSED', JSON.stringify(thresholdEvent)); - console.log(`💾 Force threshold event stored: sleep started at ${new Date(sleepStartTime).toLocaleTimeString()}`); - - // Send notification - await this.notifyPotentialSleep(inactiveTime); - - return true; - } - } + const [inactiveMinutes, rawSettings] = await Promise.all([ + this.getInactivityDuration(), + AsyncStorage.getItem(SETTINGS_KEY), + ]); + const threshold = rawSettings ? JSON.parse(rawSettings).inactivityThreshold : 30; + return inactiveMinutes >= threshold; + } - return false; - } catch (error) { - console.error('❌ Error in force threshold check:', error); - return false; - } + public async hasThresholdCrossingEvent(): Promise { + return this.forceThresholdCheck(); } - /** - * Clear any pending threshold crossing events (called when user becomes active) - */ public async clearThresholdCrossingEvent(): Promise { - try { - await AsyncStorage.removeItem('SLEEP_THRESHOLD_CROSSED'); - console.log('🚨 Cleared threshold crossing event - user is active'); - } catch (error) { - console.error('Error clearing threshold crossing event:', error); - } + await AsyncStorage.removeItem(INACTIVE_SINCE_KEY); } - /** - * Check if there's a pending threshold crossing event - */ - public async hasThresholdCrossingEvent(): Promise { + public async resetTrackingState(timestamp = Date.now()): Promise { + await AsyncStorage.multiRemove([INACTIVE_SINCE_KEY, ACTIVITY_LOG_KEY, LAST_ACTIVITY_KEY]); + await this.recordUserActivity(timestamp); + } + + private async logActivity(activity: ActivityData): Promise { try { - const event = await AsyncStorage.getItem('SLEEP_THRESHOLD_CROSSED'); - if (event) { - const parsedEvent = JSON.parse(event); - // Check if event is recent (within last 30 minutes) - const eventAge = Date.now() - parsedEvent.timestamp; - return eventAge < 1800000; // 30 minutes - } - return false; + const raw = await AsyncStorage.getItem(ACTIVITY_LOG_KEY); + const log: ActivityData[] = raw ? JSON.parse(raw) : []; + await AsyncStorage.setItem(ACTIVITY_LOG_KEY, JSON.stringify([...log, activity].slice(-250))); } catch (error) { - console.error('Error checking threshold crossing event:', error); - return false; + console.warn('Unable to persist activity diagnostic:', error); } } } diff --git a/src/services/GoogleFitService.ts b/src/services/GoogleFitService.ts index 6cf9e3c..b6b7f59 100644 --- a/src/services/GoogleFitService.ts +++ b/src/services/GoogleFitService.ts @@ -22,13 +22,9 @@ export class GoogleFitService { } try { - // For now, we'll mock this since expo-health-connect is not fully implemented - // In a real implementation, you would use: - // await initialize(); - // await requestPermission([{ accessType: 'read', recordType: 'Sleep' }]); - - console.log('Google Fit initialized successfully'); - this.hasPermission = true; + // Android sync is intentionally unavailable until the Health Connect + // permission, read, write, and deduplication flow is implemented. + this.hasPermission = false; return Promise.resolve(); } catch (error) { console.error('Failed to initialize Google Fit:', error); @@ -42,7 +38,7 @@ export class GoogleFitService { * @returns Boolean indicating if Google Fit is available */ public isAvailable(): boolean { - return Platform.OS === 'android'; + return false; } /** @@ -92,19 +88,10 @@ export class GoogleFitService { } try { - // In a real implementation, you would save sleep data to Google Fit - // For example: - // await sleep.write({ - // startTime: new Date(sleepEntry.startTime), - // endTime: new Date(sleepEntry.endTime), - // stage: sleepEntry.isAwake ? 'awake' : 'sleep' - // }); - - console.log('Saving sleep data to Google Fit:', sleepEntry); - return Promise.resolve(); + throw new Error('Android Health Connect sync is not implemented.'); } catch (error) { console.error('Error saving sleep data to Google Fit:', error); return Promise.reject(error); } } -} \ No newline at end of file +} diff --git a/src/services/HealthService.ts b/src/services/HealthService.ts index c3a54ca..cfa46eb 100644 --- a/src/services/HealthService.ts +++ b/src/services/HealthService.ts @@ -2,14 +2,26 @@ import { Platform } from 'react-native'; import { SleepEntry } from '../types/SleepEntry'; import { AppleHealthService } from './AppleHealthService'; import { GoogleFitService } from './GoogleFitService'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { HealthRecoverySnapshot } from '../types'; + +const HEALTH_SYNC_ENABLED_KEY = '@SleepDetector:healthSyncEnabled'; /** * A unified service for health integrations that works on both iOS and Android */ export class HealthService { + private static instance: HealthService; private appleHealthService: AppleHealthService | null = null; private googleFitService: GoogleFitService | null = null; private initialized: boolean = false; + private configurationLoaded = false; + private syncEnabled = false; + + public static getInstance(): HealthService { + if (!HealthService.instance) HealthService.instance = new HealthService(); + return HealthService.instance; + } constructor() { // Initialize the platform-specific service @@ -24,20 +36,18 @@ export class HealthService { * Initialize the health service for the current platform * @returns Promise that resolves when initialization is complete */ - public async initialize(): Promise { - if (this.initialized) { - return true; - } + public async initialize(requestPermissions = false): Promise { + await this.loadConfiguration(); try { if (Platform.OS === 'ios' && this.appleHealthService) { - await this.appleHealthService.initialize(); + const authorized = await this.appleHealthService.initialize(requestPermissions); this.initialized = true; - return true; + return authorized; } else if (Platform.OS === 'android' && this.googleFitService) { await this.googleFitService.initialize(); this.initialized = true; - return true; + return this.googleFitService.hasRequiredPermissions(); } // Not supported on this platform @@ -49,6 +59,25 @@ export class HealthService { } } + private async loadConfiguration(): Promise { + if (this.configurationLoaded) return; + this.syncEnabled = (await AsyncStorage.getItem(HEALTH_SYNC_ENABLED_KEY)) === 'true'; + this.configurationLoaded = true; + } + + public async setSyncEnabled(enabled: boolean): Promise { + await this.loadConfiguration(); + if (enabled && !(await this.initialize(true))) return false; + this.syncEnabled = enabled; + await AsyncStorage.setItem(HEALTH_SYNC_ENABLED_KEY, String(enabled)); + return true; + } + + public async getSyncEnabled(): Promise { + await this.loadConfiguration(); + return this.syncEnabled; + } + /** * Check if health services are available on this device * @returns Boolean indicating availability @@ -83,7 +112,7 @@ export class HealthService { */ public async getSleepData(startDate: Date, endDate: Date): Promise { if (!this.initialized) { - await this.initialize(); + await this.initialize(false); } if (Platform.OS === 'ios' && this.appleHealthService) { @@ -95,6 +124,14 @@ export class HealthService { return []; } + public async getRecoverySnapshot(startDate: Date, endDate: Date): Promise { + if (!this.initialized) await this.initialize(false); + if (Platform.OS === 'ios' && this.appleHealthService) { + return this.appleHealthService.getRecoverySnapshot(startDate, endDate); + } + return { startTime: startDate.getTime(), endTime: endDate.getTime() }; + } + /** * Save sleep data to the health service * @param sleepEntry The sleep entry to save @@ -102,7 +139,7 @@ export class HealthService { */ public async saveSleepData(sleepEntry: SleepEntry): Promise { if (!this.initialized) { - await this.initialize(); + await this.initialize(false); } if (Platform.OS === 'ios' && this.appleHealthService) { @@ -126,4 +163,4 @@ export class HealthService { } return 'Unknown Health Service'; } -} \ No newline at end of file +} diff --git a/src/services/NotificationService.ts b/src/services/NotificationService.ts index 3001abc..1c5780f 100644 --- a/src/services/NotificationService.ts +++ b/src/services/NotificationService.ts @@ -1,9 +1,7 @@ import * as Notifications from 'expo-notifications'; -import * as Device from 'expo-device'; import { Platform } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; -const NOTIFICATION_TOKEN_KEY = 'notification-token'; const NOTIFICATION_SETTINGS_KEY = 'notification-settings'; // Notification settings interface @@ -15,9 +13,9 @@ interface NotificationSettings { } const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = { - sleepDetectionEnabled: true, - wakeDetectionEnabled: true, - sleepRemindersEnabled: true, + sleepDetectionEnabled: false, + wakeDetectionEnabled: false, + sleepRemindersEnabled: false, bedtimeReminderHour: 22, }; @@ -54,9 +52,6 @@ export class NotificationService { // Load settings await this.loadSettings(); - // Request permissions - await this.requestPermissions(); - // Set up notification channels for Android await this.setupNotificationChannels(); @@ -76,12 +71,7 @@ export class NotificationService { /** * Request notification permissions */ - private async requestPermissions(): Promise { - if (!Device.isDevice) { - console.log('Must use physical device for notifications'); - return; - } - + public async requestPermissions(): Promise { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; @@ -91,18 +81,9 @@ export class NotificationService { } if (finalStatus !== 'granted') { - console.log('Failed to get push token for push notification!'); - return; - } - - // Get push token for development - try { - const token = (await Notifications.getExpoPushTokenAsync()).data; - await AsyncStorage.setItem(NOTIFICATION_TOKEN_KEY, token); - console.log('Push token:', token); - } catch (error) { - console.error('Failed to get push token:', error); + return false; } + return true; } /** @@ -414,7 +395,7 @@ export class NotificationService { const fullMessage = `${contextualMessage} -Sleep quality: ${sleepQuality} ${qualityEmoji} +Duration assessment: ${sleepQuality} ${qualityEmoji} ${qualityAdvice}`; await Notifications.scheduleNotificationAsync({ @@ -451,6 +432,7 @@ ${qualityAdvice}`; */ public async scheduleBedtimeReminder(): Promise { if (!this.settings.sleepRemindersEnabled) return; + if (!(await this.requestPermissions())) return; try { // Cancel existing bedtime reminders @@ -478,6 +460,7 @@ ${qualityAdvice}`; console.log(`✅ Bedtime reminder scheduled for ${reminderHour}:00`); } catch (error) { console.error('❌ Failed to schedule bedtime reminder:', error); + throw error; } } @@ -486,6 +469,9 @@ ${qualityAdvice}`; */ public async sendTestNotification(): Promise { try { + if (!(await this.requestPermissions())) { + throw new Error('Notification permission was not granted.'); + } const currentHour = new Date().getHours(); let testTitle = ''; let testBody = ''; @@ -524,6 +510,7 @@ ${qualityAdvice}`; console.log(`✅ Test notification sent: ${testTitle} at ${currentHour}:xx`); } catch (error) { console.error('❌ Failed to send test notification:', error); + throw error; } } diff --git a/src/services/SleepAnalysisService.ts b/src/services/SleepAnalysisService.ts new file mode 100644 index 0000000..681e1ca --- /dev/null +++ b/src/services/SleepAnalysisService.ts @@ -0,0 +1,389 @@ +import { + LocalModelAssessment, + SleepAnalysisDecision, + SleepAnalysisFeatures, + SleepEvidence, + SleepSession, +} from "../types"; +import { SleepEntry } from "../types/SleepEntry"; +import { LocalSleepIntelligence } from "./local/LocalSleepIntelligence"; + +const MINUTE = 60_000; +const MAX_CANDIDATE_MINUTES = 20 * 60; +const MAX_SLEEP_STAGE_GAP = 90 * MINUTE; + +export interface SleepAnalysisInput { + startTime: number; + endTime: number; + historicalSessions?: SleepSession[]; + healthKitOverlapRatio?: number; + watchLowMotionRatio?: number; + watchHeartRateDropPercent?: number; + allowLocalModel?: boolean; +} + +const clamp = (value: number, minimum: number, maximum: number): number => + Math.min(maximum, Math.max(minimum, value)); + +const minuteOfDay = (timestamp: number): number => { + const date = new Date(timestamp); + return date.getHours() * 60 + date.getMinutes(); +}; + +const circularDistance = (left: number, right: number): number => { + const distance = Math.abs(left - right) % 1440; + return Math.min(distance, 1440 - distance); +}; + +const circularMean = (values: number[]): number | undefined => { + if (values.length === 0) return undefined; + const radians = values.map((value) => (value / 1440) * Math.PI * 2); + const x = + radians.reduce((total, value) => total + Math.cos(value), 0) / + values.length; + const y = + radians.reduce((total, value) => total + Math.sin(value), 0) / + values.length; + const angle = Math.atan2(y, x); + return Math.round( + (((angle < 0 ? angle + Math.PI * 2 : angle) / (Math.PI * 2)) * 1440) % 1440, + ); +}; + +export interface VerifiedSleepWindow { + startTime: number; + endTime: number; + asleepMinutes: number; +} + +/** + * Finds the strongest contiguous Health sleep block inside an app-away window. + * Stage samples are commonly split into REM/core/deep intervals, so short awake + * gaps are kept in the same night while unrelated naps remain separate. + */ +export function strongestHealthSleepWindow( + entries: SleepEntry[], + startTime: number, + endTime: number, +): VerifiedSleepWindow | null { + if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) { + return null; + } + const intervals = entries + .filter(entry => + !entry.isAwake && + Number.isFinite(entry.startTime) && + Number.isFinite(entry.endTime), + ) + .map(entry => ({ + startTime: Math.max(startTime, entry.startTime), + endTime: Math.min(endTime, entry.endTime), + })) + .filter(interval => interval.endTime > interval.startTime) + .sort((left, right) => left.startTime - right.startTime); + + const clusters: Array<{ startTime: number; endTime: number; intervals: typeof intervals }> = []; + for (const interval of intervals) { + const current = clusters[clusters.length - 1]; + if (!current || interval.startTime - current.endTime > MAX_SLEEP_STAGE_GAP) { + clusters.push({ + startTime: interval.startTime, + endTime: interval.endTime, + intervals: [interval], + }); + } else { + current.endTime = Math.max(current.endTime, interval.endTime); + current.intervals.push(interval); + } + } + + const ranked = clusters.map(cluster => { + let asleepMilliseconds = 0; + let coveredUntil = cluster.startTime; + for (const interval of cluster.intervals) { + const uncoveredStart = Math.max(coveredUntil, interval.startTime); + if (interval.endTime > uncoveredStart) { + asleepMilliseconds += interval.endTime - uncoveredStart; + coveredUntil = interval.endTime; + } + } + return { ...cluster, asleepMilliseconds }; + }).sort((left, right) => right.asleepMilliseconds - left.asleepMilliseconds); + + const strongest = ranked[0]; + if (!strongest || strongest.asleepMilliseconds < 30 * MINUTE) return null; + return { + startTime: strongest.startTime, + endTime: strongest.endTime, + asleepMinutes: strongest.asleepMilliseconds / MINUTE, + }; +} + +/** Calculates unique asleep-stage coverage without double-counting sources. */ +export function healthSleepOverlapRatio( + entries: SleepEntry[], + startTime: number, + endTime: number, +): number { + if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) { + return 0; + } + const intervals = entries + .filter( + (entry) => + !entry.isAwake && + Number.isFinite(entry.startTime) && + Number.isFinite(entry.endTime), + ) + .map((entry) => [ + Math.max(startTime, entry.startTime), + Math.min(endTime, entry.endTime), + ] as const) + .filter(([start, end]) => end > start) + .sort((left, right) => left[0] - right[0]); + + let covered = 0; + let currentStart: number | undefined; + let currentEnd: number | undefined; + for (const [intervalStart, intervalEnd] of intervals) { + if (currentStart === undefined || currentEnd === undefined) { + currentStart = intervalStart; + currentEnd = intervalEnd; + } else if (intervalStart <= currentEnd) { + currentEnd = Math.max(currentEnd, intervalEnd); + } else { + covered += currentEnd - currentStart; + currentStart = intervalStart; + currentEnd = intervalEnd; + } + } + if (currentStart !== undefined && currentEnd !== undefined) { + covered += currentEnd - currentStart; + } + return clamp(covered / (endTime - startTime), 0, 1); +} + +export function buildSleepAnalysisFeatures( + input: SleepAnalysisInput, +): SleepAnalysisFeatures { + const history = (input.historicalSessions ?? []).filter( + (session) => + Number.isFinite(session.startTime) && + Number.isFinite(session.endTime) && + (session.endTime ?? 0) > session.startTime, + ); + const recentHistory = history + .sort((left, right) => (right.endTime ?? 0) - (left.endTime ?? 0)) + .slice(0, 28); + + return { + startTime: input.startTime, + endTime: input.endTime, + durationMinutes: (input.endTime - input.startTime) / MINUTE, + startMinuteOfDay: minuteOfDay(input.startTime), + endMinuteOfDay: minuteOfDay(input.endTime), + historicalSessionCount: recentHistory.length, + historicalBedtimeMinute: circularMean( + recentHistory.map((session) => minuteOfDay(session.startTime)), + ), + historicalWakeMinute: circularMean( + recentHistory.map((session) => minuteOfDay(session.endTime!)), + ), + healthKitOverlapRatio: input.healthKitOverlapRatio, + watchLowMotionRatio: input.watchLowMotionRatio, + watchHeartRateDropPercent: input.watchHeartRateDropPercent, + }; +} + +export function deterministicSleepAnalysis( + features: SleepAnalysisFeatures, +): SleepAnalysisDecision { + if ( + !Number.isFinite(features.startTime) || + !Number.isFinite(features.endTime) || + features.endTime <= features.startTime || + !Number.isFinite(features.durationMinutes) + ) { + throw new Error( + "Sleep analysis requires a finite interval with an end after its start.", + ); + } + + const evidence: SleepEvidence[] = [ + { + kind: "app-away", + weight: 15, + detail: "Sleep Detector was not in the foreground during this interval.", + }, + ]; + let score = 15; + + if (features.durationMinutes >= 90 && features.durationMinutes <= 720) { + score += 15; + } else if (features.durationMinutes >= 20 && features.durationMinutes < 90) { + score += 5; + } else if (features.durationMinutes > MAX_CANDIDATE_MINUTES) { + score -= 20; + } + + const bedtimeLike = + features.startMinuteOfDay >= 18 * 60 || features.startMinuteOfDay <= 4 * 60; + const wakeLike = + features.endMinuteOfDay >= 4 * 60 && features.endMinuteOfDay <= 13 * 60; + if (bedtimeLike) score += 10; + if (wakeLike) score += 10; + + if ( + features.historicalSessionCount >= 3 && + features.historicalBedtimeMinute !== undefined && + features.historicalWakeMinute !== undefined + ) { + const bedtimeDistance = circularDistance( + features.startMinuteOfDay, + features.historicalBedtimeMinute, + ); + const wakeDistance = circularDistance( + features.endMinuteOfDay, + features.historicalWakeMinute, + ); + const scheduleWeight = Math.round( + clamp(20 - (bedtimeDistance + wakeDistance) / 24, 0, 20), + ); + if (scheduleWeight > 0) { + score += scheduleWeight; + evidence.push({ + kind: "historical-schedule", + weight: scheduleWeight, + detail: + "The interval is close to the user’s recent confirmed sleep schedule.", + }); + } + } + + const healthOverlap = clamp(features.healthKitOverlapRatio ?? 0, 0, 1); + if (healthOverlap >= 0.25) { + const weight = Math.round(35 * healthOverlap); + score += weight; + evidence.push({ + kind: "healthkit-sleep", + weight, + detail: "Apple Health contains overlapping asleep-stage evidence.", + }); + } + + const lowMotion = clamp(features.watchLowMotionRatio ?? 0, 0, 1); + if (lowMotion >= 0.5) { + const weight = Math.round(20 * lowMotion); + score += weight; + evidence.push({ + kind: "watch-low-motion", + weight, + detail: "Apple Watch observed sustained low wrist motion.", + }); + } + + const heartRateDrop = clamp(features.watchHeartRateDropPercent ?? 0, 0, 40); + if (heartRateDrop >= 5) { + const weight = Math.round(clamp(heartRateDrop / 2, 3, 12)); + score += weight; + evidence.push({ + kind: "watch-heart-rate", + weight, + detail: "Apple Watch observed a heart-rate change consistent with rest.", + }); + } + + const hasStrongEvidence = healthOverlap >= 0.5 || lowMotion >= 0.7; + const confidence = Math.round(clamp(score, 5, hasStrongEvidence ? 95 : 55)); + const classification = + confidence >= 75 + ? "likely-sleep" + : confidence < 25 + ? "unlikely-sleep" + : "uncertain"; + + return { + startTime: features.startTime, + endTime: features.endTime, + confidence, + classification, + source: "deterministic", + explanation: hasStrongEvidence + ? "The estimate combines app inactivity with independent health or Watch evidence." + : "This estimate is based on app inactivity and schedule context, so its times require review.", + evidence, + requiresConfirmation: !hasStrongEvidence, + }; +} + +export function applyLocalModelAssessment( + deterministic: SleepAnalysisDecision, + assessment: LocalModelAssessment, +): SleepAnalysisDecision { + const hasStrongEvidence = deterministic.evidence.some( + (item) => + item.kind === "healthkit-sleep" || item.kind === "watch-low-motion", + ); + const maximumConfidence = hasStrongEvidence ? 95 : 60; + const confidence = Math.round( + clamp( + deterministic.confidence * 0.7 + + clamp(assessment.confidence, 0, 100) * 0.3, + 5, + maximumConfidence, + ), + ); + const maxBoundaryAdjustment = hasStrongEvidence ? 90 : 30; + const startOffset = clamp( + assessment.suggestedStartOffsetMinutes, + -maxBoundaryAdjustment, + maxBoundaryAdjustment, + ); + const endOffset = clamp( + assessment.suggestedEndOffsetMinutes, + -maxBoundaryAdjustment, + maxBoundaryAdjustment, + ); + const proposedStart = deterministic.startTime + startOffset * MINUTE; + const proposedEnd = deterministic.endTime + endOffset * MINUTE; + + return { + ...deterministic, + startTime: + proposedEnd > proposedStart ? proposedStart : deterministic.startTime, + endTime: proposedEnd > proposedStart ? proposedEnd : deterministic.endTime, + confidence, + classification: + confidence >= 75 + ? "likely-sleep" + : confidence < 25 + ? "unlikely-sleep" + : "uncertain", + source: "apple-foundation-model", + explanation: assessment.explanation.trim() || deterministic.explanation, + // Model reasoning is advisory and never becomes independent evidence. + requiresConfirmation: deterministic.requiresConfirmation, + }; +} + +export async function analyzeSleepCandidate( + input: SleepAnalysisInput, +): Promise { + const features = buildSleepAnalysisFeatures(input); + const deterministic = deterministicSleepAnalysis(features); + if (input.allowLocalModel === false) return deterministic; + + const availability = await LocalSleepIntelligence.getAvailability(); + if (!availability.available) return deterministic; + + try { + const assessment = await LocalSleepIntelligence.analyze(features); + return applyLocalModelAssessment(deterministic, assessment); + } catch (error) { + console.warn( + "On-device sleep analysis was unavailable; using deterministic analysis.", + error, + ); + return deterministic; + } +} diff --git a/src/services/SleepDetectionService.ts b/src/services/SleepDetectionService.ts new file mode 100644 index 0000000..2c73798 --- /dev/null +++ b/src/services/SleepDetectionService.ts @@ -0,0 +1,83 @@ +import { format } from 'date-fns'; +import { ActivityRecord, DailySleepSummary, SleepSession, SleepStatus } from '../types'; + +export interface DetectedSleepSession { + startTime: number; + endTime: number; + confidence: number; +} + +export function detectSleepSession( + inactiveStart: number, + resumedAt: number, + thresholdMinutes: number, +): DetectedSleepSession | null { + const thresholdMs = Math.max(1, thresholdMinutes) * 60_000; + const duration = resumedAt - inactiveStart; + if (!Number.isFinite(duration) || duration <= thresholdMs) return null; + + return { + // The threshold validates the candidate; it is not part of the sleep interval. + // A user must confirm this heuristic before it is recorded. + startTime: inactiveStart, + endTime: resumedAt, + confidence: 50, + }; +} + +/** Builds sessions from transitions and assigns each one to its wake-up date. */ +export function buildDailySleepSummaries(records: ActivityRecord[]): DailySleepSummary[] { + const sorted = [...records].sort((a, b) => a.timestamp - b.timestamp); + const summaries = new Map(); + let asleepRecord: ActivityRecord | null = null; + + for (const record of sorted) { + if (record.status === SleepStatus.ASLEEP) { + asleepRecord = record; + continue; + } + + if (!asleepRecord || record.timestamp <= asleepRecord.timestamp) continue; + + const confidence = (asleepRecord.confidence + record.confidence) / 2; + const date = format(record.timestamp, 'yyyy-MM-dd'); + const summary = summaries.get(date) ?? { date, totalSleepMinutes: 0, sleepPeriods: [] }; + summary.sleepPeriods.push({ + start: asleepRecord.timestamp, + end: record.timestamp, + confidence, + }); + summary.totalSleepMinutes += (record.timestamp - asleepRecord.timestamp) / 60_000; + summaries.set(date, summary); + asleepRecord = null; + } + + return [...summaries.values()].sort((a, b) => a.date.localeCompare(b.date)); +} + +/** Builds daily totals from durable completed sessions, assigning nights to wake date. */ +export function buildDailySleepSummariesFromSessions( + sessions: SleepSession[], +): DailySleepSummary[] { + const summaries = new Map(); + const completed = sessions.filter(session => + Number.isFinite(session.startTime) && + Number.isFinite(session.endTime) && + (session.endTime ?? 0) > session.startTime, + ); + + for (const session of completed) { + const endTime = session.endTime!; + const date = format(endTime, 'yyyy-MM-dd'); + const summary = summaries.get(date) ?? { date, totalSleepMinutes: 0, sleepPeriods: [] }; + summary.sleepPeriods.push({ + start: session.startTime, + end: endTime, + confidence: session.confidence, + }); + summary.totalSleepMinutes += (endTime - session.startTime) / 60_000; + summaries.set(date, summary); + } + + return [...summaries.values()].sort((left, right) => left.date.localeCompare(right.date)); +} diff --git a/src/services/SleepSessionService.ts b/src/services/SleepSessionService.ts new file mode 100644 index 0000000..e834d26 --- /dev/null +++ b/src/services/SleepSessionService.ts @@ -0,0 +1,153 @@ +import { SleepEvidence, SleepSession, SleepSessionSource } from '../types'; + +const MINUTE = 60_000; +const PENDING_SYNC_RETRY_MS = 15 * MINUTE; +const FAILED_SYNC_RETRY_MS = 6 * 60 * MINUTE; + +const uniqueEvidence = (items: SleepEvidence[]): SleepEvidence[] => { + const seen = new Set(); + return items.filter(item => { + const key = `${item.kind}:${item.detail}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); +}; + +export function createSleepSession(input: { + id?: string; + startTime: number; + endTime: number; + confidence: number; + source: SleepSessionSource; + evidence?: SleepEvidence[]; + userConfirmed: boolean; + now?: number; +}): SleepSession { + if ( + !Number.isFinite(input.startTime) || + !Number.isFinite(input.endTime) || + input.endTime <= input.startTime + ) { + throw new Error('A sleep session must have a finite end after its start.'); + } + + const now = input.now ?? Date.now(); + return { + id: input.id ?? `sleep-${input.startTime}-${input.endTime}`, + startTime: input.startTime, + endTime: input.endTime, + confidence: Math.round(Math.min(100, Math.max(0, input.confidence))), + source: input.source, + evidence: uniqueEvidence(input.evidence ?? []), + userConfirmed: input.userConfirmed, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + createdAt: now, + updatedAt: now, + healthSyncState: 'not-requested', + }; +} + +function duplicateRatio(left: SleepSession, right: SleepSession): number { + if (left.endTime === undefined || right.endTime === undefined) return 0; + const intersection = Math.max( + 0, + Math.min(left.endTime, right.endTime) - + Math.max(left.startTime, right.startTime), + ); + const shorter = Math.min( + left.endTime - left.startTime, + right.endTime - right.startTime, + ); + return shorter > 0 ? intersection / shorter : 0; +} + +/** + * Inserts a completed session while preventing lifecycle replays from creating + * duplicate nights. Slight overlaps remain separate so a night and a nearby nap + * are never silently merged. + */ +export function upsertSleepSession( + sessions: SleepSession[], + incoming: SleepSession, +): SleepSession[] { + if (incoming.endTime === undefined || incoming.endTime <= incoming.startTime) { + throw new Error('Only completed sleep sessions can be persisted.'); + } + const incomingEnd = incoming.endTime; + + const duplicateIndex = sessions.findIndex(session => { + if (session.id === incoming.id) return true; + const boundariesMatch = + session.endTime !== undefined && + Math.abs(session.startTime - incoming.startTime) <= 5 * MINUTE && + Math.abs(session.endTime - incomingEnd) <= 5 * MINUTE; + return boundariesMatch || duplicateRatio(session, incoming) >= 0.9; + }); + + const next = [...sessions]; + if (duplicateIndex >= 0) { + const existing = next[duplicateIndex]; + const boundariesUnchanged = + existing.startTime === incoming.startTime && + existing.endTime === incoming.endTime; + next[duplicateIndex] = { + ...incoming, + id: existing.id, + source: existing.source === incoming.source ? incoming.source : 'combined', + evidence: uniqueEvidence([...existing.evidence, ...incoming.evidence]), + createdAt: Math.min(existing.createdAt, incoming.createdAt), + healthSyncState: boundariesUnchanged + ? existing.healthSyncState + : incoming.healthSyncState, + healthSyncError: boundariesUnchanged + ? existing.healthSyncError + : incoming.healthSyncError, + }; + } else { + next.push(incoming); + } + + return next.sort((left, right) => left.startTime - right.startTime); +} + +export function updateSessionSyncState( + sessions: SleepSession[], + id: string, + state: SleepSession['healthSyncState'], + error?: string, +): SleepSession[] { + return sessions.map(session => + session.id === id + ? { + ...session, + healthSyncState: state, + healthSyncError: error, + updatedAt: Date.now(), + } + : session, + ); +} + +/** Avoids retrying unavailable Health permissions on every foreground event. */ +export function shouldAttemptHealthSync( + session: SleepSession, + now = Date.now(), +): boolean { + if ( + session.endTime === undefined || + !session.userConfirmed || + session.healthSyncState === 'synced' + ) { + return false; + } + if (session.healthSyncState === 'pending') { + const lastAttempt = Number.isFinite(session.updatedAt) ? session.updatedAt : 0; + return now - lastAttempt >= PENDING_SYNC_RETRY_MS; + } + if (session.healthSyncState === 'failed') { + const lastAttempt = Number.isFinite(session.updatedAt) ? session.updatedAt : 0; + return now - lastAttempt >= FAILED_SYNC_RETRY_MS; + } + return true; +} diff --git a/src/services/SleepTrackerService.ts b/src/services/SleepTrackerService.ts index b2c75b7..8b2a759 100644 --- a/src/services/SleepTrackerService.ts +++ b/src/services/SleepTrackerService.ts @@ -3,7 +3,7 @@ import { SleepEntry } from '../types/SleepEntry'; import { HealthService } from './HealthService'; // Create a singleton instance of our health service -const healthService = new HealthService(); +const healthService = HealthService.getInstance(); // Storage key for sleep data const SLEEP_DATA_KEY = '@SleepDetector:sleepData'; @@ -61,13 +61,8 @@ export class SleepTrackerService { * Set whether health sync is enabled */ public async setHealthSyncEnabled(enabled: boolean): Promise { - this.healthSyncEnabled = enabled; - await AsyncStorage.setItem(HEALTH_SYNC_ENABLED_KEY, enabled.toString()); - - // Initialize health service if enabling - if (enabled) { - await healthService.initialize(); - } + this.healthSyncEnabled = await healthService.setSyncEnabled(enabled); + await AsyncStorage.setItem(HEALTH_SYNC_ENABLED_KEY, this.healthSyncEnabled.toString()); } /** @@ -220,4 +215,4 @@ export class SleepTrackerService { this.sleepData = []; await this.saveSleepData(); } -} \ No newline at end of file +} diff --git a/src/services/WatchDataService.ts b/src/services/WatchDataService.ts new file mode 100644 index 0000000..11d70df --- /dev/null +++ b/src/services/WatchDataService.ts @@ -0,0 +1,158 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { requireOptionalNativeModule } from "expo-modules-core"; +import { Platform } from "react-native"; + +import { WatchSleepSnapshot } from "../types"; + +const WATCH_SNAPSHOTS_KEY = "sleep-detector-watch-snapshots-v1"; + +interface WatchNativeModule { + consumeWatchSnapshots(): Promise; + getWatchState(): { supported: boolean; paired: boolean; installed: boolean }; +} + +const nativeModule = + Platform.OS === "ios" + ? requireOptionalNativeModule("SleepIntelligence") + : null; + +function isOptionalFinite(value: unknown): value is number | undefined { + return ( + value === undefined || (typeof value === "number" && Number.isFinite(value)) + ); +} + +export function parseWatchSleepSnapshot( + payload: string, +): WatchSleepSnapshot | null { + try { + const value = JSON.parse(payload) as Partial; + if ( + value.schemaVersion !== 1 || + typeof value.id !== "string" || + typeof value.generatedAt !== "number" || + typeof value.sleepStart !== "number" || + typeof value.sleepEnd !== "number" || + typeof value.totalSleepMinutes !== "number" || + typeof value.awakeMinutes !== "number" || + typeof value.coreMinutes !== "number" || + typeof value.deepMinutes !== "number" || + typeof value.remMinutes !== "number" || + typeof value.unspecifiedMinutes !== "number" || + value.source !== "apple-watch-healthkit" || + !isOptionalFinite(value.restingHeartRate) || + !isOptionalFinite(value.heartRateVariabilityMs) || + !isOptionalFinite(value.respiratoryRate) || + !isOptionalFinite(value.wristTemperatureCelsius) || + !Number.isFinite(value.generatedAt) || + !Number.isFinite(value.sleepStart) || + !Number.isFinite(value.sleepEnd) || + value.sleepEnd <= value.sleepStart || + value.totalSleepMinutes < 0 + ) { + return null; + } + return value as WatchSleepSnapshot; + } catch { + return null; + } +} + +export function watchSleepOverlapRatio( + snapshots: WatchSleepSnapshot[], + startTime: number, + endTime: number, +): number { + if (endTime <= startTime) return 0; + return snapshots.reduce((best, snapshot) => { + const overlap = Math.max( + 0, + Math.min(endTime, snapshot.sleepEnd) - + Math.max(startTime, snapshot.sleepStart), + ); + const snapshotSpan = snapshot.sleepEnd - snapshot.sleepStart; + if (snapshotSpan <= 0) return best; + + // A snapshot's outer boundaries can contain awake gaps. Weighting the + // boundary overlap by actual asleep time prevents a fragmented night from + // being treated as continuous, independently verified sleep. + const sleepEfficiency = Math.min( + 1, + Math.max(0, snapshot.totalSleepMinutes * 60_000) / snapshotSpan, + ); + const ratio = (overlap / (endTime - startTime)) * sleepEfficiency; + return Math.max(best, ratio); + }, 0); +} + +/** Selects the Watch night with the most actual asleep time in the app-away window. */ +export function strongestWatchSleepWindow( + snapshots: WatchSleepSnapshot[], + startTime: number, + endTime: number, +): { startTime: number; endTime: number; asleepMinutes: number } | null { + if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) { + return null; + } + const ranked = snapshots.flatMap(snapshot => { + const clippedStart = Math.max(startTime, snapshot.sleepStart); + const clippedEnd = Math.min(endTime, snapshot.sleepEnd); + const snapshotSpan = snapshot.sleepEnd - snapshot.sleepStart; + if (clippedEnd <= clippedStart || snapshotSpan <= 0) return []; + const overlapFraction = (clippedEnd - clippedStart) / snapshotSpan; + const asleepMinutes = Math.min( + (clippedEnd - clippedStart) / 60_000, + snapshot.totalSleepMinutes * overlapFraction, + ); + return [{ startTime: clippedStart, endTime: clippedEnd, asleepMinutes }]; + }).sort((left, right) => right.asleepMinutes - left.asleepMinutes); + + return ranked[0] && ranked[0].asleepMinutes >= 30 ? ranked[0] : null; +} + +export const WatchDataService = { + getState(): { supported: boolean; paired: boolean; installed: boolean } { + return ( + nativeModule?.getWatchState() ?? { + supported: false, + paired: false, + installed: false, + } + ); + }, + + async consumePendingSnapshots(): Promise { + const stored = await AsyncStorage.getItem(WATCH_SNAPSHOTS_KEY); + let existing: WatchSleepSnapshot[] = []; + if (stored) { + try { + const parsed = JSON.parse(stored); + if (Array.isArray(parsed)) { + existing = parsed + .map((value) => parseWatchSleepSnapshot(JSON.stringify(value))) + .filter((value): value is WatchSleepSnapshot => Boolean(value)); + } + } catch { + existing = []; + } + } + const payloads = nativeModule + ? await nativeModule.consumeWatchSnapshots() + : []; + const incoming = payloads + .map(parseWatchSleepSnapshot) + .filter((value): value is WatchSleepSnapshot => Boolean(value)); + const byId = new Map(existing.map((snapshot) => [snapshot.id, snapshot])); + incoming.forEach((snapshot) => byId.set(snapshot.id, snapshot)); + const merged = [...byId.values()] + .sort((left, right) => left.sleepStart - right.sleepStart) + .slice(-60); + await AsyncStorage.setItem(WATCH_SNAPSHOTS_KEY, JSON.stringify(merged)); + return merged; + }, + + async clear(): Promise { + await AsyncStorage.removeItem(WATCH_SNAPSHOTS_KEY); + if (nativeModule) await nativeModule.consumeWatchSnapshots(); + }, +}; diff --git a/src/services/WellnessReportService.ts b/src/services/WellnessReportService.ts new file mode 100644 index 0000000..7793996 --- /dev/null +++ b/src/services/WellnessReportService.ts @@ -0,0 +1,325 @@ +import { + DailySleepSummary, + DailyWellnessCheckIn, + DailyWellnessReport, + HealthRecoverySnapshot, + SleepStageMinutes, +} from "../types"; +import { SleepEntry } from "../types/SleepEntry"; + +export interface WellnessReportInput { + date: string; + summary?: DailySleepSummary; + healthSleepEntries?: SleepEntry[]; + recovery?: HealthRecoverySnapshot; + checkIn?: DailyWellnessCheckIn; + previousReports?: DailyWellnessReport[]; + now?: number; +} + +const clamp = (value: number, minimum: number, maximum: number): number => + Math.min(maximum, Math.max(minimum, value)); + +const average = (values: Array): number | undefined => { + const finite = values.filter( + (value): value is number => + typeof value === "number" && Number.isFinite(value), + ); + if (finite.length === 0) return undefined; + return finite.reduce((sum, value) => sum + value, 0) / finite.length; +}; + +const minuteOfDay = (timestamp: number): number => { + const date = new Date(timestamp); + return date.getHours() * 60 + date.getMinutes(); +}; + +const circularDistance = (left: number, right: number): number => { + const direct = Math.abs(left - right) % 1440; + return Math.min(direct, 1440 - direct); +}; + +const circularMean = ( + values: Array, +): number | undefined => { + const finite = values.filter( + (value): value is number => + typeof value === "number" && Number.isFinite(value), + ); + if (finite.length === 0) return undefined; + const angles = finite.map((value) => (value / 1440) * Math.PI * 2); + const x = + angles.reduce((sum, value) => sum + Math.cos(value), 0) / angles.length; + const y = + angles.reduce((sum, value) => sum + Math.sin(value), 0) / angles.length; + const angle = Math.atan2(y, x); + return ( + (((angle < 0 ? angle + Math.PI * 2 : angle) / (Math.PI * 2)) * 1440) % 1440 + ); +}; + +function mergedMinutes(entries: SleepEntry[]): number { + const sorted = entries + .filter((entry) => !entry.isAwake && entry.endTime > entry.startTime) + .map((entry) => [entry.startTime, entry.endTime] as [number, number]) + .sort((left, right) => left[0] - right[0]); + const first = sorted[0]; + if (!first) return 0; + let [start, end] = first; + let total = 0; + for (const interval of sorted.slice(1)) { + if (interval[0] <= end) { + end = Math.max(end, interval[1]); + } else { + total += end - start; + [start, end] = interval; + } + } + return (total + end - start) / 60_000; +} + +function stageMinutes(entries: SleepEntry[]): SleepStageMinutes | undefined { + if (!entries.some((entry) => entry.stage)) return undefined; + const result: SleepStageMinutes = { + awake: 0, + core: 0, + deep: 0, + rem: 0, + unspecified: 0, + }; + const staged = entries.filter( + (entry): entry is SleepEntry & { stage: NonNullable } => + Boolean(entry.stage) && entry.endTime > entry.startTime, + ); + const hasDetailedStages = staged.some(entry => + entry.stage === 'core' || entry.stage === 'deep' || entry.stage === 'rem', + ); + const usable = hasDetailedStages + ? staged.filter(entry => entry.stage !== 'unspecified') + : staged; + const boundaries = [...new Set( + usable.flatMap(entry => [entry.startTime, entry.endTime]), + )].sort((left, right) => left - right); + const stagePriority: SleepEntry['stage'][] = [ + 'awake', + 'deep', + 'rem', + 'core', + 'unspecified', + ]; + for (let index = 0; index < boundaries.length - 1; index += 1) { + const start = boundaries[index]; + const end = boundaries[index + 1]; + if (end <= start) continue; + const activeStages = new Set( + usable + .filter(entry => entry.startTime < end && entry.endTime > start) + .map(entry => entry.stage), + ); + const selected = stagePriority.find(stage => stage && activeStages.has(stage)); + if (selected) result[selected] += (end - start) / 60_000; + } + return result; +} + +function pearson(left: number[], right: number[]): number | undefined { + if (left.length !== right.length || left.length < 5) return undefined; + const leftMean = average(left)!; + const rightMean = average(right)!; + let covariance = 0; + let leftVariance = 0; + let rightVariance = 0; + for (let index = 0; index < left.length; index += 1) { + const leftDelta = left[index] - leftMean; + const rightDelta = right[index] - rightMean; + covariance += leftDelta * rightDelta; + leftVariance += leftDelta ** 2; + rightVariance += rightDelta ** 2; + } + const denominator = Math.sqrt(leftVariance * rightVariance); + return denominator > 0 ? covariance / denominator : undefined; +} + +function correlationObservation( + reports: DailyWellnessReport[], + metric: + | "heartRateVariabilityMs" + | "selfReportedStress" + | "selfReportedAnxiety", + label: string, +): string | undefined { + const pairs = reports + .filter( + (report) => + report.sleepMinutes > 0 && + typeof report[metric] === "number" && + Number.isFinite(report[metric]), + ) + .map( + (report) => [report.sleepMinutes, report[metric]!] as [number, number], + ); + const value = pearson( + pairs.map((pair) => pair[0]), + pairs.map((pair) => pair[1]), + ); + if (value === undefined || Math.abs(value) < 0.35) return undefined; + const direction = value > 0 ? "higher" : "lower"; + return `Across ${pairs.length} recorded days, longer sleep coincided with ${direction} ${label} (r=${value.toFixed(2)}). This is correlation, not causation.`; +} + +export function buildDailyWellnessReport( + input: WellnessReportInput, +): DailyWellnessReport { + const healthEntries = input.healthSleepEntries ?? []; + const asleepEntries = healthEntries.filter((entry) => !entry.isAwake); + const healthMinutes = mergedMinutes(asleepEntries); + const localMinutes = input.summary?.totalSleepMinutes ?? 0; + const sleepMinutes = healthMinutes > 0 ? healthMinutes : localMinutes; + const starts = + asleepEntries.length > 0 + ? asleepEntries.map((entry) => entry.startTime) + : (input.summary?.sleepPeriods.map((period) => period.start) ?? []); + const ends = + asleepEntries.length > 0 + ? asleepEntries.map((entry) => entry.endTime) + : (input.summary?.sleepPeriods.map((period) => period.end) ?? []); + const sleepStart = starts.length > 0 ? Math.min(...starts) : undefined; + const sleepEnd = ends.length > 0 ? Math.max(...ends) : undefined; + const stages = stageMinutes(healthEntries); + const previous = (input.previousReports ?? []).slice(-28); + + const bedtimeBaseline = circularMean( + previous.map((report) => + report.sleepStart ? minuteOfDay(report.sleepStart) : undefined, + ), + ); + const wakeBaseline = circularMean( + previous.map((report) => + report.sleepEnd ? minuteOfDay(report.sleepEnd) : undefined, + ), + ); + const timingDeviation = + sleepStart !== undefined && + sleepEnd !== undefined && + bedtimeBaseline !== undefined && + wakeBaseline !== undefined + ? circularDistance(minuteOfDay(sleepStart), bedtimeBaseline) + + circularDistance(minuteOfDay(sleepEnd), wakeBaseline) + : undefined; + const sleepConsistencyScore = Math.round( + timingDeviation === undefined + ? 50 + : clamp(100 - timingDeviation / 3.6, 0, 100), + ); + const timeInWindow = + sleepStart !== undefined && sleepEnd !== undefined + ? (sleepEnd - sleepStart) / 60_000 + : undefined; + const continuity = + timeInWindow && timeInWindow > 0 + ? Math.round(clamp((sleepMinutes / timeInWindow) * 100, 0, 100)) + : undefined; + + const hrvBaseline = average( + previous.map((report) => report.heartRateVariabilityMs), + ); + const heartRateBaseline = average( + previous.map((report) => report.restingHeartRate), + ); + const hrv = input.recovery?.heartRateVariabilityMs; + const heartRate = input.recovery?.restingHeartRate; + let recoverySignal: DailyWellnessReport["recoverySignal"] = "limited-data"; + if (previous.length >= 3 && (hrv !== undefined || heartRate !== undefined)) { + const below = + (hrv !== undefined && + hrvBaseline !== undefined && + hrv < hrvBaseline * 0.85) || + (heartRate !== undefined && + heartRateBaseline !== undefined && + heartRate > heartRateBaseline * 1.1); + const above = + (hrv !== undefined && + hrvBaseline !== undefined && + hrv > hrvBaseline * 1.15) || + (heartRate !== undefined && + heartRateBaseline !== undefined && + heartRate < heartRateBaseline * 0.9); + recoverySignal = below ? "below-usual" : above ? "above-usual" : "typical"; + } + + const observations: string[] = []; + if (sleepMinutes > 0) { + observations.push( + sleepMinutes >= 420 + ? `Recorded sleep was ${Math.round(sleepMinutes)} minutes.` + : `Recorded sleep was ${Math.round(sleepMinutes)} minutes, below the general seven-hour reference used for this wellness summary.`, + ); + } + if (recoverySignal === "below-usual") { + observations.push( + "One or more recovery signals were below the user’s recent baseline; many factors can affect these measurements.", + ); + } + + const reportsForCorrelation: DailyWellnessReport[] = [ + ...previous, + { + date: input.date, + sleepStart, + sleepEnd, + sleepMinutes, + sleepConsistencyScore, + sleepContinuityScore: continuity, + sleepStages: stages, + restingHeartRate: heartRate, + heartRateVariabilityMs: hrv, + respiratoryRate: input.recovery?.respiratoryRate, + wristTemperatureCelsius: input.recovery?.wristTemperatureCelsius, + selfReportedStress: input.checkIn?.stressLevel, + selfReportedAnxiety: input.checkIn?.anxietyLevel, + recoverySignal, + observations: [], + modelSource: "deterministic", + generatedAt: input.now ?? Date.now(), + }, + ]; + [ + correlationObservation( + reportsForCorrelation, + "heartRateVariabilityMs", + "HRV", + ), + correlationObservation( + reportsForCorrelation, + "selfReportedStress", + "self-reported stress", + ), + correlationObservation( + reportsForCorrelation, + "selfReportedAnxiety", + "self-reported anxiety", + ), + ].forEach((observation) => { + if (observation) observations.push(observation); + }); + + return { + date: input.date, + sleepStart, + sleepEnd, + sleepMinutes, + sleepConsistencyScore, + sleepContinuityScore: continuity, + sleepStages: stages, + restingHeartRate: heartRate, + heartRateVariabilityMs: hrv, + respiratoryRate: input.recovery?.respiratoryRate, + wristTemperatureCelsius: input.recovery?.wristTemperatureCelsius, + selfReportedStress: input.checkIn?.stressLevel, + selfReportedAnxiety: input.checkIn?.anxietyLevel, + recoverySignal, + observations, + modelSource: "deterministic", + generatedAt: input.now ?? Date.now(), + }; +} diff --git a/src/services/__tests__/BackgroundActivityService.spec.ts b/src/services/__tests__/BackgroundActivityService.spec.ts new file mode 100644 index 0000000..8279ea6 --- /dev/null +++ b/src/services/__tests__/BackgroundActivityService.spec.ts @@ -0,0 +1,46 @@ +jest.mock('@react-native-async-storage/async-storage', () => + require('@react-native-async-storage/async-storage/jest/async-storage-mock'), +); + +import AsyncStorage from '@react-native-async-storage/async-storage'; + +import { BackgroundActivityService } from '../BackgroundActivityService'; + +describe('BackgroundActivityService', () => { + const service = BackgroundActivityService.getInstance(); + + beforeEach(async () => { + await AsyncStorage.clear(); + jest.restoreAllMocks(); + }); + + it('preserves the first background boundary and completes it at resume', async () => { + await service.recordAppInactive(1_000); + await service.recordAppInactive(2_000); + + await expect(service.consumeInactivePeriod(61_000)).resolves.toEqual({ + startTime: 1_000, + endTime: 61_000, + durationMinutes: 1, + }); + await expect(service.consumeInactivePeriod(62_000)).resolves.toBeNull(); + }); + + it('rejects malformed and future boundaries after a clock change', async () => { + await AsyncStorage.setItem('sleep-detector-inactive-since', 'not-a-time'); + await expect(service.consumeInactivePeriod(10_000)).resolves.toBeNull(); + + await service.recordAppInactive(20_000); + await expect(service.consumeInactivePeriod(10_000)).resolves.toBeNull(); + }); + + it('returns the completed period even when cleanup storage fails', async () => { + await service.recordAppInactive(1_000); + jest.spyOn(AsyncStorage, 'removeItem').mockRejectedValueOnce(new Error('disk busy')); + + await expect(service.consumeInactivePeriod(61_000)).resolves.toMatchObject({ + startTime: 1_000, + endTime: 61_000, + }); + }); +}); diff --git a/src/services/__tests__/SleepAnalysisService.spec.ts b/src/services/__tests__/SleepAnalysisService.spec.ts new file mode 100644 index 0000000..07ffc3c --- /dev/null +++ b/src/services/__tests__/SleepAnalysisService.spec.ts @@ -0,0 +1,123 @@ +jest.mock("../local/LocalSleepIntelligence", () => ({ + LocalSleepIntelligence: { + getAvailability: jest.fn(async () => ({ + available: false, + reason: "native-module-unavailable", + })), + analyze: jest.fn(), + }, +})); + +import { + applyLocalModelAssessment, + buildSleepAnalysisFeatures, + deterministicSleepAnalysis, + healthSleepOverlapRatio, + strongestHealthSleepWindow, +} from "../SleepAnalysisService"; + +const localTimestamp = ( + year: number, + month: number, + day: number, + hour: number, +): number => new Date(year, month - 1, day, hour, 0, 0, 0).getTime(); + +describe("SleepAnalysisService", () => { + it("keeps app-only inactivity uncertain and capped at low confidence", () => { + const features = buildSleepAnalysisFeatures({ + startTime: localTimestamp(2026, 7, 1, 23), + endTime: localTimestamp(2026, 7, 2, 7), + }); + + const result = deterministicSleepAnalysis(features); + + expect(result.classification).toBe("uncertain"); + expect(result.confidence).toBeLessThanOrEqual(55); + expect(result.requiresConfirmation).toBe(true); + expect(result.evidence.map((item) => item.kind)).toEqual(["app-away"]); + }); + + it("allows independent HealthKit overlap to produce a strong decision", () => { + const features = buildSleepAnalysisFeatures({ + startTime: localTimestamp(2026, 7, 1, 23), + endTime: localTimestamp(2026, 7, 2, 7), + healthKitOverlapRatio: 1, + }); + + const result = deterministicSleepAnalysis(features); + + expect(result.classification).toBe("likely-sleep"); + expect(result.confidence).toBeGreaterThanOrEqual(75); + expect(result.requiresConfirmation).toBe(false); + expect( + result.evidence.some((item) => item.kind === "healthkit-sleep"), + ).toBe(true); + }); + + it("never lets model opinion turn weak evidence into high confidence", () => { + const deterministic = deterministicSleepAnalysis( + buildSleepAnalysisFeatures({ + startTime: localTimestamp(2026, 7, 1, 23), + endTime: localTimestamp(2026, 7, 2, 7), + }), + ); + + const result = applyLocalModelAssessment(deterministic, { + classification: "likely-sleep", + confidence: 100, + suggestedStartOffsetMinutes: -300, + suggestedEndOffsetMinutes: 300, + explanation: "The schedule resembles sleep.", + }); + + expect(result.confidence).toBeLessThanOrEqual(60); + expect(result.classification).toBe("uncertain"); + expect(result.startTime).toBe(deterministic.startTime - 30 * 60_000); + expect(result.endTime).toBe(deterministic.endTime + 30 * 60_000); + expect(result.requiresConfirmation).toBe(true); + }); + + it("rejects invalid intervals before any model is consulted", () => { + const features = buildSleepAnalysisFeatures({ + startTime: 1000, + endTime: 1000, + }); + expect(() => deterministicSleepAnalysis(features)).toThrow( + /end after its start/, + ); + }); + + it("merges overlapping Health sleep stages without double-counting", () => { + const entries = [ + { id: "core", startTime: 1_000, endTime: 5_000, source: "Watch", confidence: 95, isAwake: false }, + { id: "deep", startTime: 3_000, endTime: 7_000, source: "Watch", confidence: 95, isAwake: false }, + { id: "awake", startTime: 7_000, endTime: 9_000, source: "Watch", confidence: 95, isAwake: true }, + ]; + + expect(healthSleepOverlapRatio(entries, 1_000, 9_000)).toBeCloseTo(0.75); + }); + + it("uses the strongest Health sleep block to refine broad app inactivity", () => { + const hour = 60 * 60_000; + const entries = [ + { id: "nap", startTime: 2 * hour, endTime: 2.5 * hour, source: "Watch", confidence: 95, isAwake: false }, + { id: "core", startTime: 5 * hour, endTime: 8 * hour, source: "Watch", confidence: 95, isAwake: false }, + { id: "deep", startTime: 8.25 * hour, endTime: 10 * hour, source: "Watch", confidence: 95, isAwake: false }, + { id: "awake", startTime: 8 * hour, endTime: 8.25 * hour, source: "Watch", confidence: 95, isAwake: true }, + ]; + + expect(strongestHealthSleepWindow(entries, 0, 12 * hour)).toEqual({ + startTime: 5 * hour, + endTime: 10 * hour, + asleepMinutes: 285, + }); + }); + + it("does not refine a candidate from only a brief Health sample", () => { + const entries = [ + { id: "brief", startTime: 0, endTime: 29 * 60_000, source: "Watch", confidence: 95, isAwake: false }, + ]; + expect(strongestHealthSleepWindow(entries, 0, 8 * 60 * 60_000)).toBeNull(); + }); +}); diff --git a/src/services/__tests__/SleepDetectionService.spec.ts b/src/services/__tests__/SleepDetectionService.spec.ts new file mode 100644 index 0000000..9d14a02 --- /dev/null +++ b/src/services/__tests__/SleepDetectionService.spec.ts @@ -0,0 +1,91 @@ +import { ActivityRecord, SleepStatus } from '../../types'; +import { + buildDailySleepSummaries, + buildDailySleepSummariesFromSessions, + detectSleepSession, +} from '../SleepDetectionService'; +import { createSleepSession } from '../SleepSessionService'; + +const record = (status: SleepStatus, timestamp: number, confidence = 100): ActivityRecord => ({ + id: `${status}-${timestamp}`, + status, + timestamp, + confidence, +}); + +describe('detectSleepSession', () => { + it('does not create sleep before the inactivity threshold', () => { + const start = Date.now(); + expect(detectSleepSession(start, start + 29 * 60_000, 30)).toBeNull(); + }); + + it('requires inactivity to exceed the configured threshold', () => { + const start = Date.now(); + expect(detectSleepSession(start, start + 30 * 60_000, 30)).toBeNull(); + expect(detectSleepSession(start, start + 30 * 60_000 + 1, 30)).not.toBeNull(); + }); + + it('creates a completed session when use resumes after prolonged inactivity', () => { + const inactiveAt = new Date(2026, 6, 15, 22, 0).getTime(); + const resumedAt = new Date(2026, 6, 16, 6, 0).getTime(); + const session = detectSleepSession(inactiveAt, resumedAt, 30); + + expect(session).not.toBeNull(); + expect(session?.startTime).toBe(inactiveAt); + expect(session?.endTime).toBe(resumedAt); + expect(session?.confidence).toBe(50); + }); +}); + +describe('buildDailySleepSummaries', () => { + it('pairs transitions and assigns overnight sleep to the wake-up date', () => { + const asleepAt = new Date(2026, 6, 15, 23, 30).getTime(); + const awakeAt = new Date(2026, 6, 16, 1, 30).getTime(); + const summaries = buildDailySleepSummaries([ + record(SleepStatus.AWAKE, asleepAt - 60_000), + record(SleepStatus.ASLEEP, asleepAt, 90), + record(SleepStatus.AWAKE, awakeAt, 100), + ]); + + expect(summaries).toHaveLength(1); + expect(summaries[0].date).toBe('2026-07-16'); + expect(summaries[0].sleepPeriods[0]).toMatchObject({ + start: asleepAt, + end: awakeAt, + }); + expect(summaries[0].totalSleepMinutes).toBeCloseTo(120); + }); + + it('ignores an unfinished sleep record until a wake transition exists', () => { + expect(buildDailySleepSummaries([ + record(SleepStatus.ASLEEP, Date.now()), + ])).toEqual([]); + }); + + it('reports elapsed duration independently from confidence', () => { + const asleepAt = new Date(2026, 6, 15, 1, 0).getTime(); + const awakeAt = asleepAt + 2 * 60 * 60_000; + const [summary] = buildDailySleepSummaries([ + record(SleepStatus.ASLEEP, asleepAt, 20), + record(SleepStatus.AWAKE, awakeAt, 40), + ]); + + expect(summary.totalSleepMinutes).toBe(120); + expect(summary.sleepPeriods[0].confidence).toBe(30); + }); + + it('builds totals from durable sessions when transition records are unavailable', () => { + const session = createSleepSession({ + startTime: new Date(2026, 6, 1, 23, 0).getTime(), + endTime: new Date(2026, 6, 2, 7, 0).getTime(), + confidence: 88, + source: 'combined', + userConfirmed: true, + }); + + const [summary] = buildDailySleepSummariesFromSessions([session]); + expect(summary.date).toBe('2026-07-02'); + expect(summary.totalSleepMinutes).toBe(480); + expect(summary.sleepPeriods[0].confidence).toBe(88); + }); +}); diff --git a/src/services/__tests__/SleepSessionService.spec.ts b/src/services/__tests__/SleepSessionService.spec.ts new file mode 100644 index 0000000..2d7b973 --- /dev/null +++ b/src/services/__tests__/SleepSessionService.spec.ts @@ -0,0 +1,113 @@ +import { + createSleepSession, + shouldAttemptHealthSync, + updateSessionSyncState, + upsertSleepSession, +} from '../SleepSessionService'; + +describe('SleepSessionService', () => { + it('rejects invalid completed sessions', () => { + expect(() => + createSleepSession({ + startTime: 200, + endTime: 100, + confidence: 50, + source: 'app-inactivity', + userConfirmed: true, + }), + ).toThrow(); + }); + + it('deduplicates lifecycle replays without combining nearby sessions', () => { + const first = createSleepSession({ + startTime: 1_000_000, + endTime: 29_800_000, + confidence: 55, + source: 'app-inactivity', + userConfirmed: true, + now: 1, + }); + const replay = createSleepSession({ + startTime: first.startTime + 60_000, + endTime: first.endTime! - 60_000, + confidence: 60, + source: 'apple-watch', + userConfirmed: true, + now: 2, + }); + const nap = createSleepSession({ + startTime: first.endTime! + 10 * 60_000, + endTime: first.endTime! + 50 * 60_000, + confidence: 70, + source: 'manual', + userConfirmed: true, + now: 3, + }); + + const result = upsertSleepSession(upsertSleepSession([first], replay), nap); + + expect(result).toHaveLength(2); + expect(result[0].id).toBe(first.id); + expect(result[0].source).toBe('combined'); + }); + + it('preserves a successful sync when the same boundaries are replayed', () => { + const first = { + ...createSleepSession({ + startTime: 1, + endTime: 10_000, + confidence: 80, + source: 'manual', + userConfirmed: true, + }), + healthSyncState: 'synced' as const, + }; + const replay = createSleepSession({ + startTime: 1, + endTime: 10_000, + confidence: 80, + source: 'manual', + userConfirmed: true, + }); + + expect(upsertSleepSession([first], replay)[0].healthSyncState).toBe('synced'); + }); + + it('records retryable Health sync failures', () => { + const session = createSleepSession({ + startTime: 1, + endTime: 10_000, + confidence: 80, + source: 'manual', + userConfirmed: true, + }); + const [updated] = updateSessionSyncState([session], session.id, 'failed', 'Denied'); + expect(updated.healthSyncState).toBe('failed'); + expect(updated.healthSyncError).toBe('Denied'); + }); +}); + +describe('shouldAttemptHealthSync', () => { + const now = 10 * 60 * 60_000; + const session = createSleepSession({ + startTime: 1_000, + endTime: 2_000, + confidence: 90, + source: 'manual', + userConfirmed: true, + now, + }); + + it('backs off failed and in-flight writes while retrying stale ones', () => { + const failed = { + ...updateSessionSyncState([session], session.id, 'failed', 'Denied')[0], + updatedAt: now, + }; + expect(shouldAttemptHealthSync(failed, now + 60_000)).toBe(false); + expect(shouldAttemptHealthSync(failed, now + 6 * 60 * 60_000)).toBe(true); + + const pending = { ...session, healthSyncState: 'pending' as const, updatedAt: now }; + expect(shouldAttemptHealthSync(pending, now + 14 * 60_000)).toBe(false); + expect(shouldAttemptHealthSync(pending, now + 15 * 60_000)).toBe(true); + }); +}); diff --git a/src/services/__tests__/WatchDataService.spec.ts b/src/services/__tests__/WatchDataService.spec.ts new file mode 100644 index 0000000..1b84fba --- /dev/null +++ b/src/services/__tests__/WatchDataService.spec.ts @@ -0,0 +1,84 @@ +jest.mock("expo-modules-core", () => ({ + requireOptionalNativeModule: jest.fn(() => null), +})); +jest.mock("@react-native-async-storage/async-storage", () => + require("@react-native-async-storage/async-storage/jest/async-storage-mock"), +); + +import { + parseWatchSleepSnapshot, + strongestWatchSleepWindow, + watchSleepOverlapRatio, +} from "../WatchDataService"; + +const snapshot = { + schemaVersion: 1, + id: "watch-1", + generatedAt: 2_000, + sleepStart: 1_000, + sleepEnd: 5_000, + totalSleepMinutes: 4 / 60, + awakeMinutes: 0, + coreMinutes: 4 / 60, + deepMinutes: 0, + remMinutes: 0, + unspecifiedMinutes: 0, + source: "apple-watch-healthkit", +} as const; + +describe("WatchDataService", () => { + it("accepts a valid versioned Watch snapshot", () => { + expect(parseWatchSleepSnapshot(JSON.stringify(snapshot))).toEqual(snapshot); + }); + + it("rejects invalid or reversed Watch snapshots", () => { + expect(parseWatchSleepSnapshot("not-json")).toBeNull(); + expect( + parseWatchSleepSnapshot( + JSON.stringify({ ...snapshot, sleepEnd: snapshot.sleepStart }), + ), + ).toBeNull(); + }); + + it("calculates overlap against the complete candidate interval", () => { + expect(watchSleepOverlapRatio([snapshot], 0, 5_000)).toBeCloseTo(0.8); + expect(watchSleepOverlapRatio([snapshot], 6_000, 7_000)).toBe(0); + }); + + it("discounts awake gaps inside a Watch sleep episode", () => { + const fragmented = { + ...snapshot, + sleepStart: 0, + sleepEnd: 8 * 60 * 60_000, + totalSleepMinutes: 4 * 60, + }; + + expect( + watchSleepOverlapRatio([fragmented], 0, 8 * 60 * 60_000), + ).toBeCloseTo(0.5); + }); + + it("selects the Watch night with the most asleep time", () => { + const hour = 60 * 60_000; + const short = { + ...snapshot, + id: "short", + sleepStart: hour, + sleepEnd: 2 * hour, + totalSleepMinutes: 45, + }; + const overnight = { + ...snapshot, + id: "overnight", + sleepStart: 4 * hour, + sleepEnd: 12 * hour, + totalSleepMinutes: 420, + }; + + expect(strongestWatchSleepWindow([short, overnight], 0, 14 * hour)).toEqual({ + startTime: 4 * hour, + endTime: 12 * hour, + asleepMinutes: 420, + }); + }); +}); diff --git a/src/services/__tests__/WellnessReportService.spec.ts b/src/services/__tests__/WellnessReportService.spec.ts new file mode 100644 index 0000000..fc096eb --- /dev/null +++ b/src/services/__tests__/WellnessReportService.spec.ts @@ -0,0 +1,106 @@ +import { buildDailyWellnessReport } from "../WellnessReportService"; +import { DailyWellnessReport } from "../../types"; + +describe("WellnessReportService", () => { + it("uses HealthKit asleep stages without counting in-bed or awake time", () => { + const report = buildDailyWellnessReport({ + date: "2026-07-18", + healthSleepEntries: [ + { + id: "core", + startTime: 0, + endTime: 60 * 60_000, + isAwake: false, + confidence: 95, + source: "Watch", + stage: "core", + }, + { + id: "awake", + startTime: 60 * 60_000, + endTime: 90 * 60_000, + isAwake: true, + confidence: 95, + source: "Watch", + stage: "awake", + }, + { + id: "deep", + startTime: 90 * 60_000, + endTime: 150 * 60_000, + isAwake: false, + confidence: 95, + source: "Watch", + stage: "deep", + }, + ], + now: 123, + }); + + expect(report.sleepMinutes).toBe(120); + expect(report.sleepStages?.awake).toBe(30); + expect(report.sleepStages?.core).toBe(60); + expect(report.sleepStages?.deep).toBe(60); + expect(report.generatedAt).toBe(123); + }); + + it("labels recovery as limited data until a personal baseline exists", () => { + const report = buildDailyWellnessReport({ + date: "2026-07-18", + recovery: { startTime: 0, endTime: 1, heartRateVariabilityMs: 30 }, + }); + expect(report.recoverySignal).toBe("limited-data"); + }); + + it("does not double-count a broad app sample over detailed Watch stages", () => { + const base = { + startTime: 0, + endTime: 60 * 60_000, + isAwake: false, + confidence: 95, + }; + const report = buildDailyWellnessReport({ + date: "2026-07-18", + healthSleepEntries: [ + { ...base, id: "app", source: "Sleep Detector", stage: "unspecified" }, + { ...base, id: "watch", source: "Watch", stage: "core" }, + ], + }); + + expect(report.sleepMinutes).toBe(60); + expect(report.sleepStages?.core).toBe(60); + expect(report.sleepStages?.unspecified).toBe(0); + }); + + it("states correlation without claiming causation", () => { + const previousReports: DailyWellnessReport[] = [1, 2, 3, 4].map( + (value) => ({ + date: `2026-07-0${value}`, + sleepMinutes: value * 60, + sleepConsistencyScore: 80, + selfReportedStress: 6 - value, + recoverySignal: "typical", + observations: [], + modelSource: "deterministic", + generatedAt: value, + }), + ); + const report = buildDailyWellnessReport({ + date: "2026-07-05", + summary: { + date: "2026-07-05", + totalSleepMinutes: 300, + sleepPeriods: [{ start: 0, end: 300 * 60_000, confidence: 80 }], + }, + checkIn: { date: "2026-07-05", stressLevel: 1, updatedAt: 1 }, + previousReports, + }); + + expect( + report.observations.some((item) => item.includes("self-reported stress")), + ).toBe(true); + expect( + report.observations.some((item) => item.includes("not causation")), + ).toBe(true); + }); +}); diff --git a/src/services/exportService.ts b/src/services/exportService.ts index 9f22869..425aa27 100644 --- a/src/services/exportService.ts +++ b/src/services/exportService.ts @@ -1,4 +1,4 @@ -import * as FileSystem from 'expo-file-system'; +import { File, Paths } from 'expo-file-system'; import * as Sharing from 'expo-sharing'; import { format } from 'date-fns'; @@ -7,17 +7,17 @@ export const exportDataToJson = async (data: string): Promise => { try { // Create filename with current date const fileName = `sleep-data-${format(new Date(), 'yyyy-MM-dd-HHmmss')}.json`; - const filePath = `${FileSystem.documentDirectory}${fileName}`; + const file = new File(Paths.document, fileName); // Write data to file - await FileSystem.writeAsStringAsync(filePath, data); + file.write(data); // Check if sharing is available const isSharingAvailable = await Sharing.isAvailableAsync(); if (isSharingAvailable) { // Share the file - await Sharing.shareAsync(filePath); + await Sharing.shareAsync(file.uri); } else { console.log('Sharing is not available on this device'); // Could implement alternative export method here @@ -49,17 +49,17 @@ export const exportDataToCsv = async ( // Create filename with current date const fileName = `sleep-data-${format(new Date(), 'yyyy-MM-dd-HHmmss')}.csv`; - const filePath = `${FileSystem.documentDirectory}${fileName}`; + const file = new File(Paths.document, fileName); // Write data to file - await FileSystem.writeAsStringAsync(filePath, csvContent); + file.write(csvContent); // Check if sharing is available const isSharingAvailable = await Sharing.isAvailableAsync(); if (isSharingAvailable) { // Share the file - await Sharing.shareAsync(filePath); + await Sharing.shareAsync(file.uri); } else { console.log('Sharing is not available on this device'); // Could implement alternative export method here @@ -68,4 +68,4 @@ export const exportDataToCsv = async ( console.error('Error exporting data to CSV:', error); throw error; } -}; \ No newline at end of file +}; diff --git a/src/services/local/LocalSleepIntelligence.ts b/src/services/local/LocalSleepIntelligence.ts new file mode 100644 index 0000000..ffb5c3a --- /dev/null +++ b/src/services/local/LocalSleepIntelligence.ts @@ -0,0 +1,58 @@ +import { Platform } from "react-native"; +import { requireOptionalNativeModule } from "expo-modules-core"; +import { + LocalModelAssessment, + LocalModelAvailability, + SleepAnalysisFeatures, +} from "../../types"; + +interface NativeSleepIntelligenceModule { + getAvailability(): Promise; + analyzeCandidate(featuresJson: string): Promise; +} + +const nativeModule = + Platform.OS === "ios" + ? requireOptionalNativeModule( + "SleepIntelligence", + ) + : null; + +export const LocalSleepIntelligence = { + async getAvailability(): Promise { + if (Platform.OS !== "ios") { + return { available: false, reason: "unsupported-platform" }; + } + if (!nativeModule) { + return { available: false, reason: "native-module-unavailable" }; + } + try { + return await nativeModule.getAvailability(); + } catch { + return { available: false, reason: "unknown" }; + } + }, + + async analyze( + features: SleepAnalysisFeatures, + ): Promise { + if (!nativeModule) + throw new Error("The native on-device model module is unavailable."); + const response = await nativeModule.analyzeCandidate( + JSON.stringify(features), + ); + const parsed = JSON.parse(response) as Partial; + if ( + typeof parsed.classification !== "string" || + typeof parsed.confidence !== "number" || + typeof parsed.suggestedStartOffsetMinutes !== "number" || + typeof parsed.suggestedEndOffsetMinutes !== "number" || + typeof parsed.explanation !== "string" + ) { + throw new Error( + "The on-device model returned an invalid sleep assessment.", + ); + } + return parsed as LocalModelAssessment; + }, +}; diff --git a/src/types/SleepEntry.ts b/src/types/SleepEntry.ts index 3ed6b57..db240f2 100644 --- a/src/types/SleepEntry.ts +++ b/src/types/SleepEntry.ts @@ -6,29 +6,32 @@ export interface SleepEntry { * Unique identifier for the entry */ id: string; - + /** * Start time of the sleep entry in milliseconds since epoch */ startTime: number; - + /** * End time of the sleep entry in milliseconds since epoch */ endTime: number; - + /** * Whether the user was awake during this entry */ isAwake: boolean; - + /** * Confidence level of the detection (0-100) */ confidence: number; - + /** * Source of the sleep data (e.g., 'app', 'Apple Health', 'Google Fit') */ source: string; -} \ No newline at end of file + + /** Apple Health sleep stage when available. */ + stage?: "awake" | "core" | "deep" | "rem" | "unspecified"; +} diff --git a/src/types/index.ts b/src/types/index.ts index b48257f..21e8045 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,8 +2,8 @@ // Sleep status enum export enum SleepStatus { - AWAKE = 'awake', - ASLEEP = 'asleep', + AWAKE = "awake", + ASLEEP = "asleep", } // Activity record interface @@ -25,15 +25,30 @@ export interface DailySleepSummary { }[]; } +// A heuristic interval that must be reviewed before it becomes health data. +export interface SleepCandidate { + id: string; + startTime: number; + endTime: number; + confidence: number; + createdAt: number; + originalStartTime?: number; + originalEndTime?: number; + classification?: "likely-sleep" | "uncertain" | "unlikely-sleep"; + analysisSource?: "deterministic" | "apple-foundation-model"; + explanation?: string; + evidence?: import("./sleepAnalysis").SleepEvidence[]; +} + // App settings export interface AppSettings { inactivityThreshold: number; // Minutes of inactivity before considered asleep useMachineLearning: boolean; // Whether to use enhanced ML-based detection considerTimeOfDay: boolean; // Consider typical sleep hours in detection - sensitivityLevel: 'low' | 'medium' | 'high'; // Movement detection sensitivity + sensitivityLevel: "low" | "medium" | "high"; // Movement detection sensitivity adaptiveThreshold: boolean; // Adapt threshold based on time and patterns napDetection: boolean; // Enable enhanced nap detection - backgroundPersistence: 'normal' | 'aggressive' | 'maximum'; // Background service persistence + backgroundPersistence: "normal" | "aggressive" | "maximum"; // Background service persistence // New advanced options for enhanced control smartWakeupWindow: boolean; // Enable smart wake-up within a time window confidenceBasedAdjustment: boolean; // Adjust thresholds based on detection confidence @@ -58,4 +73,6 @@ export type MainTabParamList = { Today: undefined; History: undefined; Stats: undefined; -}; \ No newline at end of file +}; + +export * from "./sleepAnalysis"; diff --git a/src/types/sleepAnalysis.ts b/src/types/sleepAnalysis.ts new file mode 100644 index 0000000..ee03f15 --- /dev/null +++ b/src/types/sleepAnalysis.ts @@ -0,0 +1,149 @@ +export type SleepEvidenceKind = + | "app-away" + | "historical-schedule" + | "healthkit-sleep" + | "watch-low-motion" + | "watch-heart-rate" + | "user-confirmed" + | "user-edited"; + +export interface SleepEvidence { + kind: SleepEvidenceKind; + weight: number; + detail: string; +} + +export type SleepAnalysisSource = "deterministic" | "apple-foundation-model"; + +export interface SleepAnalysisFeatures { + startTime: number; + endTime: number; + durationMinutes: number; + startMinuteOfDay: number; + endMinuteOfDay: number; + historicalSessionCount: number; + historicalBedtimeMinute?: number; + historicalWakeMinute?: number; + healthKitOverlapRatio?: number; + watchLowMotionRatio?: number; + watchHeartRateDropPercent?: number; +} + +export interface SleepAnalysisDecision { + startTime: number; + endTime: number; + confidence: number; + classification: "likely-sleep" | "uncertain" | "unlikely-sleep"; + source: SleepAnalysisSource; + explanation: string; + evidence: SleepEvidence[]; + requiresConfirmation: boolean; +} + +export interface LocalModelAvailability { + available: boolean; + reason: + | "available" + | "unsupported-platform" + | "unsupported-os" + | "device-not-eligible" + | "model-not-ready" + | "native-module-unavailable" + | "unknown"; +} + +export interface LocalModelAssessment { + classification: SleepAnalysisDecision["classification"]; + confidence: number; + suggestedStartOffsetMinutes: number; + suggestedEndOffsetMinutes: number; + explanation: string; +} + +export type SleepSessionSource = + | "app-inactivity" + | "manual" + | "apple-watch" + | "healthkit" + | "combined"; +export type HealthSyncState = "not-requested" | "pending" | "synced" | "failed"; + +export interface SleepSession { + id: string; + startTime: number; + endTime?: number; + confidence: number; + source: SleepSessionSource; + evidence: SleepEvidence[]; + userConfirmed: boolean; + timezone: string; + createdAt: number; + updatedAt: number; + healthSyncState: HealthSyncState; + healthSyncError?: string; +} + +export interface SleepStageMinutes { + awake: number; + core: number; + deep: number; + rem: number; + unspecified: number; +} + +export interface WatchSleepSnapshot { + schemaVersion: 1; + id: string; + generatedAt: number; + sleepStart: number; + sleepEnd: number; + totalSleepMinutes: number; + awakeMinutes: number; + coreMinutes: number; + deepMinutes: number; + remMinutes: number; + unspecifiedMinutes: number; + restingHeartRate?: number; + heartRateVariabilityMs?: number; + respiratoryRate?: number; + wristTemperatureCelsius?: number; + source: "apple-watch-healthkit"; +} + +export interface HealthRecoverySnapshot { + startTime: number; + endTime: number; + restingHeartRate?: number; + heartRateVariabilityMs?: number; + respiratoryRate?: number; + wristTemperatureCelsius?: number; +} + +/** Wellness observations only. These values must never be presented as a diagnosis. */ +export interface DailyWellnessReport { + date: string; + sleepStart?: number; + sleepEnd?: number; + sleepMinutes: number; + sleepConsistencyScore: number; + sleepContinuityScore?: number; + sleepStages?: SleepStageMinutes; + restingHeartRate?: number; + heartRateVariabilityMs?: number; + respiratoryRate?: number; + wristTemperatureCelsius?: number; + selfReportedStress?: number; + selfReportedAnxiety?: number; + recoverySignal: "limited-data" | "below-usual" | "typical" | "above-usual"; + observations: string[]; + modelSource: SleepAnalysisSource; + generatedAt: number; +} + +export interface DailyWellnessCheckIn { + date: string; + stressLevel?: 1 | 2 | 3 | 4 | 5; + anxietyLevel?: 1 | 2 | 3 | 4 | 5; + note?: string; + updatedAt: number; +} diff --git a/targets/watch/Assets.xcassets/$accent.colorset/Contents.json b/targets/watch/Assets.xcassets/$accent.colorset/Contents.json new file mode 100644 index 0000000..3346396 --- /dev/null +++ b/targets/watch/Assets.xcassets/$accent.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors": [ + { + "color": { + "color-space": "display-p3", + "components": { + "red": 0.4235294117647059, + "green": 0.16862745098039217, + "blue": 1, + "alpha": 1 + } + }, + "idiom": "universal" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/targets/watch/Assets.xcassets/AppIcon.appiconset/Contents.json b/targets/watch/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..50936f9 --- /dev/null +++ b/targets/watch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "filename": "App-Icon-1024x1024@1x.png", + "idiom": "universal", + "size": "1024x1024", + "platform": "watchos" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/targets/watch/Info.plist b/targets/watch/Info.plist new file mode 100644 index 0000000..dfa61ab --- /dev/null +++ b/targets/watch/Info.plist @@ -0,0 +1,12 @@ + + + + + NSHealthShareUsageDescription + Sleep Detector reads Apple Watch sleep stages and optional recovery signals to create your private daily sleep report. + NSHealthUpdateUsageDescription + Sleep Detector may save sleep periods you confirm to Apple Health when Health sync is enabled on the companion iPhone app. + WKCompanionAppBundleIdentifier + com.sleepdetector.app + + diff --git a/targets/watch/WatchHealthStore.swift b/targets/watch/WatchHealthStore.swift new file mode 100644 index 0000000..f6d4b34 --- /dev/null +++ b/targets/watch/WatchHealthStore.swift @@ -0,0 +1,232 @@ +import Foundation +import HealthKit + +@MainActor +final class WatchHealthStore: ObservableObject { + @Published private(set) var latestSnapshot: WatchSleepSnapshot? + @Published private(set) var isLoading = false + @Published private(set) var errorMessage: String? + + private let healthStore = HKHealthStore() + private let accessRequestedKey = "sleep-detector-watch-health-requested" + + var hasRequestedAccess: Bool { + UserDefaults.standard.bool(forKey: accessRequestedKey) + } + + func requestAccessAndRefresh() async { + guard HKHealthStore.isHealthDataAvailable() else { + errorMessage = "Health data is unavailable on this Watch." + return + } + + isLoading = true + errorMessage = nil + defer { isLoading = false } + do { + try await healthStore.requestAuthorization(toShare: [], read: readableTypes()) + UserDefaults.standard.set(true, forKey: accessRequestedKey) + try await refresh() + } catch { + errorMessage = "Health access failed: \(error.localizedDescription)" + } + } + + func refreshIfAuthorized() async { + guard hasRequestedAccess else { return } + isLoading = true + errorMessage = nil + defer { isLoading = false } + do { + try await refresh() + } catch { + errorMessage = "Could not refresh sleep data: \(error.localizedDescription)" + } + } + + private func refresh() async throws { + guard let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) else { return } + let end = Date() + let start = Calendar.current.date(byAdding: .hour, value: -36, to: end) ?? end.addingTimeInterval(-36 * 3600) + let samples = try await categorySamples(type: sleepType, start: start, end: end) + guard let snapshot = try await makeSnapshot(from: samples, start: start, end: end) else { + latestSnapshot = nil + return + } + latestSnapshot = snapshot + WatchTransferService.shared.enqueue(snapshot) + } + + private func readableTypes() -> Set { + var types = Set() + if let type = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) { types.insert(type) } + if let type = HKObjectType.quantityType(forIdentifier: .restingHeartRate) { types.insert(type) } + if let type = HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN) { types.insert(type) } + if let type = HKObjectType.quantityType(forIdentifier: .respiratoryRate) { types.insert(type) } + if let type = HKObjectType.quantityType(forIdentifier: .appleSleepingWristTemperature) { types.insert(type) } + return types + } + + private func categorySamples( + type: HKCategoryType, + start: Date, + end: Date + ) async throws -> [HKCategorySample] { + try await withCheckedThrowingContinuation { continuation in + let predicate = HKQuery.predicateForSamples(withStart: start, end: end, options: .strictStartDate) + let query = HKSampleQuery( + sampleType: type, + predicate: predicate, + limit: HKObjectQueryNoLimit, + sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)] + ) { _, samples, error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: samples as? [HKCategorySample] ?? []) + } + } + healthStore.execute(query) + } + } + + private func quantityAverage( + identifier: HKQuantityTypeIdentifier, + unit: HKUnit, + start: Date, + end: Date + ) async throws -> Double? { + guard let type = HKObjectType.quantityType(forIdentifier: identifier) else { return nil } + return try await withCheckedThrowingContinuation { continuation in + let predicate = HKQuery.predicateForSamples(withStart: start, end: end, options: .strictStartDate) + let query = HKStatisticsQuery(quantityType: type, quantitySamplePredicate: predicate, options: .discreteAverage) { + _, result, error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: result?.averageQuantity()?.doubleValue(for: unit)) + } + } + healthStore.execute(query) + } + } + + private func makeSnapshot( + from samples: [HKCategorySample], + start queryStart: Date, + end queryEnd: Date + ) async throws -> WatchSleepSnapshot? { + let allSleepSamples = samples.filter { sample in + guard let value = HKCategoryValueSleepAnalysis(rawValue: sample.value) else { return false } + return value == .asleepUnspecified || value == .asleepCore || value == .asleepDeep || value == .asleepREM + }.sorted { $0.startDate < $1.startDate } + let sleepSamples = latestSleepEpisode(from: allSleepSamples) + guard let first = sleepSamples.first, let last = sleepSamples.last else { return nil } + + let sleepStart = first.startDate + let sleepEnd = last.endDate + let episodeSamples = samples.filter { + $0.endDate > sleepStart && $0.startDate < sleepEnd + } + let hasDetailedStages = episodeSamples.contains { + $0.value == HKCategoryValueSleepAnalysis.asleepCore.rawValue || + $0.value == HKCategoryValueSleepAnalysis.asleepDeep.rawValue || + $0.value == HKCategoryValueSleepAnalysis.asleepREM.rawValue + } + let totalSleep = mergedDuration(sleepSamples.map { ($0.startDate, $0.endDate) }) + func minutes(for value: HKCategoryValueSleepAnalysis) -> Double { + if value == .asleepUnspecified && hasDetailedStages { return 0 } + let intervals = episodeSamples + .filter { $0.value == value.rawValue } + .map { + let start = max($0.startDate, sleepStart) + let end = min($0.endDate, sleepEnd) + return (start, end) + } + .filter { $0.1 > $0.0 } + return mergedDuration(intervals) + } + + async let restingHeartRate = quantityAverage( + identifier: .restingHeartRate, + unit: HKUnit.count().unitDivided(by: .minute()), + start: sleepStart, + end: sleepEnd + ) + async let hrv = quantityAverage( + identifier: .heartRateVariabilitySDNN, + unit: .secondUnit(with: .milli), + start: sleepStart, + end: sleepEnd + ) + async let respiratoryRate = quantityAverage( + identifier: .respiratoryRate, + unit: HKUnit.count().unitDivided(by: .minute()), + start: sleepStart, + end: sleepEnd + ) + async let wristTemperature = quantityAverage( + identifier: .appleSleepingWristTemperature, + unit: .degreeCelsius(), + start: max(queryStart, sleepStart), + end: min(queryEnd, sleepEnd) + ) + + let metrics = try await (restingHeartRate, hrv, respiratoryRate, wristTemperature) + return WatchSleepSnapshot( + schemaVersion: 1, + id: UUID().uuidString, + generatedAt: Date(), + sleepStart: sleepStart, + sleepEnd: sleepEnd, + totalSleepMinutes: totalSleep, + awakeMinutes: minutes(for: .awake), + coreMinutes: minutes(for: .asleepCore), + deepMinutes: minutes(for: .asleepDeep), + remMinutes: minutes(for: .asleepREM), + unspecifiedMinutes: minutes(for: .asleepUnspecified), + restingHeartRate: metrics.0, + heartRateVariabilityMs: metrics.1, + respiratoryRate: metrics.2, + wristTemperatureCelsius: metrics.3, + source: "apple-watch-healthkit" + ) + } + + private func mergedDuration(_ intervals: [(Date, Date)]) -> Double { + let sorted = intervals.sorted { $0.0 < $1.0 } + guard var current = sorted.first else { return 0 } + var seconds = 0.0 + for interval in sorted.dropFirst() { + if interval.0 <= current.1 { + current.1 = max(current.1, interval.1) + } else { + seconds += current.1.timeIntervalSince(current.0) + current = interval + } + } + seconds += current.1.timeIntervalSince(current.0) + return seconds / 60 + } + + /** Selects the most recent episode instead of combining separate nights. */ + private func latestSleepEpisode(from samples: [HKCategorySample]) -> [HKCategorySample] { + guard let first = samples.first else { return [] } + let maximumGap: TimeInterval = 3 * 60 * 60 + var episodes: [[HKCategorySample]] = [[first]] + for sample in samples.dropFirst() { + let previousEnd = episodes[episodes.count - 1] + .map(\.endDate) + .max() ?? sample.startDate + if sample.startDate.timeIntervalSince(previousEnd) <= maximumGap { + episodes[episodes.count - 1].append(sample) + } else { + episodes.append([sample]) + } + } + return episodes.max { + ($0.map(\.endDate).max() ?? .distantPast) < + ($1.map(\.endDate).max() ?? .distantPast) + } ?? [] + } +} diff --git a/targets/watch/WatchSleepSnapshot.swift b/targets/watch/WatchSleepSnapshot.swift new file mode 100644 index 0000000..395be17 --- /dev/null +++ b/targets/watch/WatchSleepSnapshot.swift @@ -0,0 +1,25 @@ +import Foundation + +struct WatchSleepSnapshot: Codable, Identifiable { + let schemaVersion: Int + let id: String + let generatedAt: Date + let sleepStart: Date + let sleepEnd: Date + let totalSleepMinutes: Double + let awakeMinutes: Double + let coreMinutes: Double + let deepMinutes: Double + let remMinutes: Double + let unspecifiedMinutes: Double + let restingHeartRate: Double? + let heartRateVariabilityMs: Double? + let respiratoryRate: Double? + let wristTemperatureCelsius: Double? + let source: String + + var formattedDuration: String { + let total = max(0, Int(totalSleepMinutes.rounded())) + return "\(total / 60)h \(total % 60)m" + } +} diff --git a/targets/watch/WatchTransferService.swift b/targets/watch/WatchTransferService.swift new file mode 100644 index 0000000..946763e --- /dev/null +++ b/targets/watch/WatchTransferService.swift @@ -0,0 +1,65 @@ +import Foundation +import WatchConnectivity + +final class WatchTransferService: NSObject, WCSessionDelegate { + static let shared = WatchTransferService() + private let pendingKey = "sleep-detector-pending-watch-payloads-v1" + private let lock = NSLock() + + private override init() { + super.init() + } + + func activate() { + guard WCSession.isSupported() else { return } + let session = WCSession.default + session.delegate = self + if session.activationState != .activated { + session.activate() + } + } + + func enqueue(_ snapshot: WatchSleepSnapshot) { + activate() + guard let payloadData = try? JSONEncoder.sleepDetector.encode(snapshot), + let payload = String(data: payloadData, encoding: .utf8) else { return } + lock.lock() + var pending = UserDefaults.standard.stringArray(forKey: pendingKey) ?? [] + if !pending.contains(payload) { pending.append(payload) } + UserDefaults.standard.set(Array(pending.suffix(5)), forKey: pendingKey) + flushLocked() + lock.unlock() + } + + func session( + _ session: WCSession, + activationDidCompleteWith activationState: WCSessionActivationState, + error: Error? + ) { + guard activationState == .activated, error == nil else { return } + lock.lock() + flushLocked() + lock.unlock() + } + + private func flushLocked() { + guard WCSession.default.activationState == .activated else { return } + let pending = UserDefaults.standard.stringArray(forKey: pendingKey) ?? [] + pending.forEach { payload in + WCSession.default.transferUserInfo([ + "kind": "sleep-health-snapshot", + "schemaVersion": 1, + "payload": payload, + ]) + } + UserDefaults.standard.removeObject(forKey: pendingKey) + } +} + +extension JSONEncoder { + static var sleepDetector: JSONEncoder { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .millisecondsSince1970 + return encoder + } +} diff --git a/targets/watch/content.swift b/targets/watch/content.swift new file mode 100644 index 0000000..2d63ad2 --- /dev/null +++ b/targets/watch/content.swift @@ -0,0 +1,92 @@ +import SwiftUI + +struct ContentView: View { + @StateObject private var health = WatchHealthStore() + + var body: some View { + NavigationStack { + ScrollView { + VStack(alignment: .leading, spacing: 12) { + Label("Sleep Detector", systemImage: "bed.double.fill") + .font(.headline) + + if health.isLoading { + ProgressView("Reading Health…") + } else if let snapshot = health.latestSnapshot { + Text(snapshot.formattedDuration) + .font(.system(size: 28, weight: .bold, design: .rounded)) + Text("Last recorded sleep") + .font(.caption) + .foregroundStyle(.secondary) + + stageRow("Core", minutes: snapshot.coreMinutes, color: .blue) + stageRow("Deep", minutes: snapshot.deepMinutes, color: .indigo) + stageRow("REM", minutes: snapshot.remMinutes, color: .cyan) + + if let hrv = snapshot.heartRateVariabilityMs { + metricRow("HRV", value: String(format: "%.0f ms", hrv)) + } + if let heartRate = snapshot.restingHeartRate { + metricRow("Resting HR", value: String(format: "%.0f bpm", heartRate)) + } + + Text("Recovery signals are wellness observations, not a stress or anxiety diagnosis.") + .font(.caption2) + .foregroundStyle(.secondary) + } else { + Text("Allow Health access to use Apple Watch sleep stages for more precise reports.") + .font(.caption) + .foregroundStyle(.secondary) + } + + if let error = health.errorMessage { + Text(error) + .font(.caption2) + .foregroundStyle(.red) + } + + Button(health.hasRequestedAccess ? "Refresh" : "Allow Health Access") { + Task { await health.requestAccessAndRefresh() } + } + .buttonStyle(.borderedProminent) + .disabled(health.isLoading) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + .navigationTitle("Sleep") + } + .task { + WatchTransferService.shared.activate() + await health.refreshIfAuthorized() + } + } + + private func stageRow(_ label: String, minutes: Double, color: Color) -> some View { + HStack { + Circle().fill(color).frame(width: 7, height: 7) + Text(label).font(.caption) + Spacer() + Text(formatMinutes(minutes)).font(.caption.monospacedDigit()) + } + } + + private func metricRow(_ label: String, value: String) -> some View { + HStack { + Text(label).font(.caption) + Spacer() + Text(value).font(.caption.monospacedDigit()) + } + } + + private func formatMinutes(_ minutes: Double) -> String { + let total = max(0, Int(minutes.rounded())) + return "\(total / 60)h \(total % 60)m" + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/targets/watch/expo-target.config.js b/targets/watch/expo-target.config.js new file mode 100644 index 0000000..7e2b6a1 --- /dev/null +++ b/targets/watch/expo-target.config.js @@ -0,0 +1,16 @@ +/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */ +module.exports = config => ({ + type: "watch", + name: "SleepDetectorWatch", + displayName: "Sleep Detector", + bundleIdentifier: ".watch", + icon: "../../assets/icon.png", + colors: { $accent: "#6C2BFF" }, + deploymentTarget: "9.4", + frameworks: ["HealthKit", "WatchConnectivity", "SwiftUI"], + entitlements: { + "com.apple.developer.healthkit": true, + "com.apple.security.application-groups": + config.ios.entitlements["com.apple.security.application-groups"], + }, +}); diff --git a/targets/watch/index.swift b/targets/watch/index.swift new file mode 100644 index 0000000..d578588 --- /dev/null +++ b/targets/watch/index.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct SleepDetectorWatchApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/targets/watch/preview/Preview Assets.xcassets/Contents.json b/targets/watch/preview/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/targets/watch/preview/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +}