diff --git a/.github/actions/setup-demo/action.yml b/.github/actions/setup-demo/action.yml new file mode 100644 index 00000000..e6ed1c47 --- /dev/null +++ b/.github/actions/setup-demo/action.yml @@ -0,0 +1,60 @@ +name: 'Setup Demo' +description: 'Installs toolchains, builds the SDK, sets up the demo app, and creates the .env file' +inputs: + onesignal-app-id: + description: 'OneSignal App ID for the demo .env' + required: true + onesignal-api-key: + description: 'OneSignal API Key for the demo .env' + required: true + install-pods: + description: 'Whether to run pod update for iOS' + required: false + default: 'false' +runs: + using: 'composite' + steps: + - name: Set up Vite+ + uses: voidzero-dev/setup-vp@v1 + with: + cache: true + run-install: true + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Cache bun dependencies + uses: actions/cache@v5 + with: + path: examples/demo/node_modules + key: bun-${{ runner.os }}-${{ hashFiles('examples/demo/bun.lock') }} + restore-keys: bun-${{ runner.os }}- + + - name: Install and set up demo + shell: bash + working-directory: examples/demo + run: | + bun run setup + bun install + + - name: Cache CocoaPods + if: inputs.install-pods == 'true' + uses: actions/cache@v5 + with: + path: examples/demo/ios/Pods + key: pods-${{ runner.os }}-${{ hashFiles('examples/demo/ios/Podfile.lock') }} + restore-keys: pods-${{ runner.os }}- + + - name: Update CocoaPods + if: inputs.install-pods == 'true' + shell: bash + working-directory: examples/demo + run: bun run update:pods + + - name: Create demo .env + shell: bash + working-directory: examples/demo + run: | + echo "ONESIGNAL_APP_ID=${{ inputs.onesignal-app-id }}" > .env + echo "ONESIGNAL_API_KEY=${{ inputs.onesignal-api-key }}" >> .env + echo "E2E_MODE=true" >> .env diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000..22a22994 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,152 @@ +name: E2E Tests + +on: + push: + branches: + - rel/** + workflow_dispatch: + inputs: + platform: + description: 'Platform to test' + required: true + default: 'both' + type: choice + options: + - android + - ios + - both + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-android: + if: >- + github.event_name == 'push' || + github.event.inputs.platform == 'android' || + github.event.inputs.platform == 'both' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Set up demo + uses: ./.github/actions/setup-demo + with: + onesignal-app-id: ${{ vars.APPIUM_ONESIGNAL_APP_ID }} + onesignal-api-key: ${{ secrets.APPIUM_ONESIGNAL_API_KEY }} + + - name: Build release APK + working-directory: examples/demo/android + run: ./gradlew assembleRelease + + - name: Upload APK + uses: actions/upload-artifact@v7 + with: + name: demo-apk + path: examples/demo/android/app/build/outputs/apk/release/app-release.apk + retention-days: 1 + compression-level: 0 + + build-ios: + if: >- + github.event_name == 'push' || + github.event.inputs.platform == 'ios' || + github.event.inputs.platform == 'both' + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up demo + uses: ./.github/actions/setup-demo + with: + onesignal-app-id: ${{ vars.APPIUM_ONESIGNAL_APP_ID }} + onesignal-api-key: ${{ secrets.APPIUM_ONESIGNAL_API_KEY }} + install-pods: 'true' + + - name: Cache Xcode DerivedData + uses: actions/cache@v5 + with: + path: examples/demo/ios/build + key: deriveddata-${{ runner.os }}-${{ hashFiles('examples/demo/ios/Podfile.lock') }} + restore-keys: deriveddata-${{ runner.os }}- + + - name: Set up iOS codesigning + uses: OneSignal/sdk-shared/.github/actions/setup-ios-demo-codesigning@main + with: + p12-base64: ${{ secrets.APPIUM_IOS_DEV_CERT_P12_BASE64 }} + p12-password: ${{ secrets.APPIUM_IOS_DEV_CERT_PASSWORD }} + asc-key-id: ${{ secrets.APPIUM_APP_STORE_CONNECT_KEY_ID }} + asc-issuer-id: ${{ secrets.APPIUM_APP_STORE_CONNECT_ISSUER_ID }} + asc-private-key: ${{ secrets.APPIUM_APP_STORE_CONNECT_PRIVATE_KEY }} + + - name: Build signed IPA + working-directory: examples/demo/ios + run: | + xcodebuild archive \ + -workspace demo.xcworkspace \ + -scheme demo \ + -configuration Release \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath build/demo.xcarchive \ + -derivedDataPath build \ + CODE_SIGN_STYLE=Manual \ + COMPILER_INDEX_STORE_ENABLE=NO + xcodebuild -exportArchive \ + -archivePath build/demo.xcarchive \ + -exportOptionsPlist ExportOptions.plist \ + -exportPath build/ipa + + - name: Verify aps-environment in IPA + working-directory: examples/demo/ios + run: | + IPA=$(ls build/ipa/*.ipa | head -n1) + unzip -oq "$IPA" -d /tmp/ipa + APP=$(ls -d /tmp/ipa/Payload/*.app | head -n1) + codesign -d --entitlements - "$APP" 2>&1 | tee /tmp/entitlements.txt + if ! grep -q 'aps-environment' /tmp/entitlements.txt; then + echo "::error::Built IPA is missing aps-environment entitlement; push subscription will not work" + exit 1 + fi + + - name: Upload IPA + uses: actions/upload-artifact@v7 + with: + name: demo-ipa + path: examples/demo/ios/build/ipa/demo.ipa + retention-days: 1 + compression-level: 0 + + e2e-android: + needs: build-android + uses: OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main + secrets: inherit + with: + platform: android + app-artifact: demo-apk + app-filename: app-release.apk + sdk-type: react-native + build-name: react-native-android-${{ github.ref_name }}-${{ github.run_number }} + + e2e-ios: + needs: build-ios + uses: OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main + secrets: inherit + with: + platform: ios + app-artifact: demo-ipa + app-filename: demo.ipa + sdk-type: react-native + build-name: react-native-ios-${{ github.ref_name }}-${{ github.run_number }} diff --git a/.gitignore b/.gitignore index 027fb5ad..6a670398 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build dist android/build *.tgz +.rn-sdk-source.stamp # OSX # diff --git a/android/build.gradle b/android/build.gradle index 736153e9..458ec91f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -27,6 +27,12 @@ android { dependencies { implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" + // androidx.startup runs our OneSignalInitializer during Application.onCreate so we can register + // an ActivityLifecycleCallbacks before MainActivity.onResume fires. This avoids the cold-start + // race where ReactApplicationContext.getCurrentActivity() returns null and the OneSignal SDK + // ends up holding an ApplicationContext instead of the real Activity. + implementation 'androidx.startup:startup-runtime:1.1.1' + // api is used instead of implementation so the parent :app project can access any of the OneSignal Java // classes if needed. Such as com.onesignal.NotificationExtenderService // diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index a2f47b60..899feb92 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,2 +1,14 @@ - + + + + + + diff --git a/android/src/main/java/com/onesignal/rnonesignalandroid/ActivityLifecycleTracker.java b/android/src/main/java/com/onesignal/rnonesignalandroid/ActivityLifecycleTracker.java new file mode 100644 index 00000000..4079b943 --- /dev/null +++ b/android/src/main/java/com/onesignal/rnonesignalandroid/ActivityLifecycleTracker.java @@ -0,0 +1,72 @@ +package com.onesignal.rnonesignalandroid; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; +import androidx.annotation.Nullable; +import java.lang.ref.WeakReference; + +/** + * Tracks the host app's current Activity from Application.onCreate onward. + * + *

Registered very early via {@link OneSignalInitializer} (androidx.startup) so it captures the + * first {@code MainActivity.onResume} that fires before the React Native bridge has loaded the JS + * bundle. Without this, {@link com.facebook.react.bridge.ReactApplicationContext#getCurrentActivity()} + * frequently returns {@code null} during cold start in bridgeless mode, causing + * {@code RNOneSignal.initialize} to hand the OneSignal SDK an ApplicationContext instead of the + * real Activity. That in turn leaves {@code ApplicationService.current == null} and queues + * {@code requestPermission()} until the next foreground. + */ +public class ActivityLifecycleTracker implements Application.ActivityLifecycleCallbacks { + private static final ActivityLifecycleTracker INSTANCE = new ActivityLifecycleTracker(); + + private volatile WeakReference currentActivity = new WeakReference<>(null); + + private ActivityLifecycleTracker() {} + + public static ActivityLifecycleTracker getInstance() { + return INSTANCE; + } + + @Nullable + public Activity getCurrentActivity() { + return currentActivity.get(); + } + + @Override + public void onActivityCreated(Activity activity, @Nullable Bundle savedInstanceState) { + currentActivity = new WeakReference<>(activity); + } + + @Override + public void onActivityStarted(Activity activity) { + currentActivity = new WeakReference<>(activity); + } + + @Override + public void onActivityResumed(Activity activity) { + currentActivity = new WeakReference<>(activity); + } + + @Override + public void onActivityPaused(Activity activity) { + // Intentionally no-op: keep the reference so a transient overlay (e.g. permission dialog, + // PermissionsActivity) doesn't blank out the current Activity for callers that race with it. + } + + @Override + public void onActivityStopped(Activity activity) { + // Intentionally no-op for the same reason as onActivityPaused. + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) { + Activity current = currentActivity.get(); + if (current == activity) { + currentActivity = new WeakReference<>(null); + } + } +} diff --git a/android/src/main/java/com/onesignal/rnonesignalandroid/OneSignalInitializer.java b/android/src/main/java/com/onesignal/rnonesignalandroid/OneSignalInitializer.java new file mode 100644 index 00000000..5f295a6a --- /dev/null +++ b/android/src/main/java/com/onesignal/rnonesignalandroid/OneSignalInitializer.java @@ -0,0 +1,37 @@ +package com.onesignal.rnonesignalandroid; + +import android.app.Application; +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.startup.Initializer; +import java.util.Collections; +import java.util.List; + +/** + * androidx.startup entry point that registers {@link ActivityLifecycleTracker} against the host + * {@link Application} during {@code Application.onCreate}, before any Activity is created. + * + *

This does NOT initialize the OneSignal SDK itself: the App ID is supplied at runtime by JS + * via {@code OneSignal.initialize(appId)}. The job here is purely to capture the current Activity + * early so that when JS later calls initialize, {@code RNOneSignal} can hand a real Activity to + * {@code OneSignal.initWithContext}. + */ +public class OneSignalInitializer implements Initializer { + + @NonNull + @Override + public ActivityLifecycleTracker create(@NonNull Context context) { + ActivityLifecycleTracker tracker = ActivityLifecycleTracker.getInstance(); + Context appContext = context.getApplicationContext(); + if (appContext instanceof Application) { + ((Application) appContext).registerActivityLifecycleCallbacks(tracker); + } + return tracker; + } + + @NonNull + @Override + public List>> dependencies() { + return Collections.emptyList(); + } +} diff --git a/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java b/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java index 148c3cb9..4b6a3b0b 100644 --- a/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java +++ b/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java @@ -238,11 +238,21 @@ public void initialize(String appId) { } ReactApplicationContext reactContext = getReactApplicationContext(); - Context context = reactContext.getCurrentActivity(); + // Prefer the Activity captured by ActivityLifecycleTracker (registered via androidx.startup + // before MainActivity.onResume), then fall back to ReactApplicationContext's accessor and + // finally the ApplicationContext. Passing the real Activity lets the OneSignal SDK populate + // ApplicationService.current immediately, so requestPermission() can launch the OS dialog + // on the first cold-start instead of waiting for the next foreground event. + Context context = ActivityLifecycleTracker.getInstance().getCurrentActivity(); + if (context == null) { + context = reactContext.getCurrentActivity(); + } if (context == null) { context = reactContext.getApplicationContext(); } + Logging.debug( + "OneSignal initialize using context: " + context.getClass().getSimpleName(), null); OneSignal.initWithContext(context, appId); oneSignalInitDone = true; } diff --git a/examples/build_ios.md b/examples/build_ios.md index 64ad987a..d96029b7 100644 --- a/examples/build_ios.md +++ b/examples/build_ios.md @@ -271,7 +271,7 @@ The `project.pbxproj` needs native target entries for both extensions. These are **OneSignalNotificationServiceExtension target** (`com.apple.product-type.app-extension`): - Sources, Frameworks, Resources build phases -- `PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalNotificationServiceExtensionRN` (must be prefixed with the parent app bundle ID) +- `PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.NSE` (must be prefixed with the parent app bundle ID) - `CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements` - `INFOPLIST_FILE = OneSignalNotificationServiceExtension/Info.plist` - `SKIP_INSTALL = YES`, `SWIFT_VERSION = 5.0`, `IPHONEOS_DEPLOYMENT_TARGET = 13.0` @@ -279,7 +279,7 @@ The `project.pbxproj` needs native target entries for both extensions. These are **OneSignalWidgetExtension target** (`com.apple.product-type.app-extension`): - Sources, Frameworks (linking WidgetKit.framework and SwiftUI.framework), Resources build phases -- `PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalWidgetExtension` +- `PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.LA` - `INFOPLIST_FILE = OneSignalWidget/Info.plist` (note: folder is `OneSignalWidget`, not `OneSignalWidgetExtension`) - `SKIP_INSTALL = YES`, `SWIFT_VERSION = 5.0`, `IPHONEOS_DEPLOYMENT_TARGET = 16.2` (Live Activities require iOS 16.2+) diff --git a/examples/demo/.env.example b/examples/demo/.env.example index 674a938f..19ce21ac 100644 --- a/examples/demo/.env.example +++ b/examples/demo/.env.example @@ -1 +1,4 @@ -ONESIGNAL_API_KEY=your_rest_api_key +# Default App ID (used if ONESIGNAL_APP_ID is empty): 77e32082-ea27-42e3-a898-c72e141824ef +ONESIGNAL_APP_ID=your-onesignal-app-id +ONESIGNAL_API_KEY=your-onesignal-api-key +E2E_MODE=false diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index b737db09..b56bc3de 100644 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -2,146 +2,32 @@ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import React, { useEffect } from 'react'; import { StatusBar, StyleSheet, Text, View } from 'react-native'; -import { - InAppMessageClickEvent, - InAppMessageDidDismissEvent, - InAppMessageDidDisplayEvent, - InAppMessageWillDismissEvent, - InAppMessageWillDisplayEvent, - LogLevel, - NotificationClickEvent, - NotificationWillDisplayEvent, - OneSignal, -} from 'react-native-onesignal'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import Toast from 'react-native-toast-message'; import OneSignalLogo from './assets/onesignal_logo.svg'; -import { AppContextProvider } from './src/context/AppContext'; +import AppHeader from './src/components/AppHeader'; +import { OneSignalProvider } from './src/hooks/useOneSignal'; import HomeScreen from './src/screens/HomeScreen'; import SecondaryScreen from './src/screens/SecondaryScreen'; -import LogManager from './src/services/LogManager'; -import OneSignalApiService from './src/services/OneSignalApiService'; -import PreferencesService from './src/services/PreferencesService'; import TooltipHelper from './src/services/TooltipHelper'; import { AppColors } from './src/theme'; const Stack = createNativeStackNavigator(); -const log = LogManager.getInstance(); -const TAG = 'App'; function App() { useEffect(() => { - const handleIamWillDisplay = (e: InAppMessageWillDisplayEvent) => { - log.i(TAG, `IAM willDisplay: ${e.message.messageId}`); - }; - - const handleIamDidDisplay = (e: InAppMessageDidDisplayEvent) => { - log.i(TAG, `IAM didDisplay: ${e.message.messageId}`); - }; - - const handleIamWillDismiss = (e: InAppMessageWillDismissEvent) => { - log.i(TAG, `IAM willDismiss: ${e.message.messageId}`); - }; - - const handleIamDidDismiss = (e: InAppMessageDidDismissEvent) => { - log.i(TAG, `IAM didDismiss: ${e.message.messageId}`); - }; - - const handleIamClick = (e: InAppMessageClickEvent) => { - log.i(TAG, `IAM click: ${e.result.actionId ?? 'unknown'}`); - }; - - const handleNotificationClick = (e: NotificationClickEvent) => { - log.i(TAG, `Notification click: ${e.notification.title ?? ''}`); - }; - - const handlePermissionChange = (granted: boolean) => { - log.i(TAG, `Permission changed: ${granted}`); - }; - - const handleForegroundWillDisplay = (e: NotificationWillDisplayEvent) => { - log.i(TAG, `Notification foregroundWillDisplay: ${e.getNotification().title ?? ''}`); - e.getNotification().display(); - }; - - const init = async () => { - try { - const prefs = PreferencesService.getInstance(); - const [appId, consentRequired, privacyConsent, iamPaused, locationShared] = - await Promise.all([ - prefs.getAppId(), - prefs.getConsentRequired(), - prefs.getPrivacyConsent(), - prefs.getIamPaused(), - prefs.getLocationShared(), - ]); - - OneSignalApiService.getInstance().setAppId(appId); - - OneSignal.Debug.setLogLevel(LogLevel.Verbose); - OneSignal.setConsentRequired(consentRequired); - OneSignal.setConsentGiven(privacyConsent); - OneSignal.initialize(appId); - - OneSignal.LiveActivities.setupDefault({ - enablePushToStart: true, - enablePushToUpdate: true, - }); - - OneSignal.InAppMessages.setPaused(iamPaused); - OneSignal.Location.setShared(locationShared); - - // Register SDK event listeners for logging - OneSignal.InAppMessages.addEventListener('willDisplay', handleIamWillDisplay); - OneSignal.InAppMessages.addEventListener('didDisplay', handleIamDidDisplay); - OneSignal.InAppMessages.addEventListener('willDismiss', handleIamWillDismiss); - OneSignal.InAppMessages.addEventListener('didDismiss', handleIamDidDismiss); - OneSignal.InAppMessages.addEventListener('click', handleIamClick); - OneSignal.Notifications.addEventListener('click', handleNotificationClick); - OneSignal.Notifications.addEventListener('permissionChange', handlePermissionChange); - OneSignal.Notifications.addEventListener( - 'foregroundWillDisplay', - handleForegroundWillDisplay, - ); - - log.i(TAG, `OneSignal initialized with app ID: ${appId}`); - } catch (err) { - log.e(TAG, `Init error: ${String(err)}`); - } - }; - - void init(); - - // Fetch tooltips in background void TooltipHelper.getInstance().init(); - - return () => { - OneSignal.InAppMessages.removeEventListener('willDisplay', handleIamWillDisplay); - OneSignal.InAppMessages.removeEventListener('didDisplay', handleIamDidDisplay); - OneSignal.InAppMessages.removeEventListener('willDismiss', handleIamWillDismiss); - OneSignal.InAppMessages.removeEventListener('didDismiss', handleIamDidDismiss); - OneSignal.InAppMessages.removeEventListener('click', handleIamClick); - OneSignal.Notifications.removeEventListener('click', handleNotificationClick); - OneSignal.Notifications.removeEventListener('permissionChange', handlePermissionChange); - OneSignal.Notifications.removeEventListener( - 'foregroundWillDisplay', - handleForegroundWillDisplay, - ); - }; }, []); return ( - + , }} > - + ); diff --git a/examples/demo/android/gradle.properties b/examples/demo/android/gradle.properties index 9afe6159..cf10d482 100644 --- a/examples/demo/android/gradle.properties +++ b/examples/demo/android/gradle.properties @@ -25,7 +25,7 @@ android.useAndroidX=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 +reactNativeArchitectures=arm64-v8a # Use this property to enable support to the new architecture. # This will allow you to use TurboModules and the Fabric render in diff --git a/examples/demo/bun.lock b/examples/demo/bun.lock index ba93fc12..81244d3c 100644 --- a/examples/demo/bun.lock +++ b/examples/demo/bun.lock @@ -8,9 +8,9 @@ "@react-native-async-storage/async-storage": "^2.1.0", "@react-navigation/native": "^7.0.0", "@react-navigation/native-stack": "^7.0.0", - "react-native-onesignal": "file:../../react-native-onesignal.tgz", "react": "19.2.3", "react-native": "0.84.0", + "react-native-onesignal": "file:../../react-native-onesignal.tgz", "react-native-safe-area-context": "^5.5.2", "react-native-screens": "^4.0.0", "react-native-svg": "^15.8.0", @@ -52,7 +52,7 @@ "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@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.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="], + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@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.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="], "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], @@ -80,9 +80,9 @@ "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="], - "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], - "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], @@ -258,18 +258,16 @@ "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="], - "@babel/preset-env": ["@babel/preset-env@7.29.0", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w=="], + "@babel/preset-env": ["@babel/preset-env@7.29.2", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw=="], "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], - "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="], @@ -280,7 +278,7 @@ "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], - "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.6", "", {}, "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw=="], "@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="], @@ -368,13 +366,13 @@ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.84.0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-ugwSj0Gb4MYrcm8uQrQw8qHPx5RKGDLuZRAP/AuwneFizHx8YCLBEFbOYRGWgxHBRtkJ70D1o+jpIx3CK3p5lw=="], - "@react-navigation/core": ["@react-navigation/core@7.14.0", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "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.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g=="], + "@react-navigation/core": ["@react-navigation/core@7.17.2", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "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.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA=="], - "@react-navigation/elements": ["@react-navigation/elements@2.9.5", "", { "dependencies": { "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.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g=="], + "@react-navigation/elements": ["@react-navigation/elements@2.9.14", "", { "dependencies": { "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.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-lKqzu+su2pI/YIZmR7L7xdOs4UL+rVXKJAMpRMBrwInEy96SjIFst6QDGpE89Dunnu3VjVpjWfByo9f2GWBHDQ=="], - "@react-navigation/native": ["@react-navigation/native@7.1.28", "", { "dependencies": { "@react-navigation/core": "^7.14.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ=="], + "@react-navigation/native": ["@react-navigation/native@7.2.2", "", { "dependencies": { "@react-navigation/core": "^7.17.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w=="], - "@react-navigation/native-stack": ["@react-navigation/native-stack@7.13.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-5OOp1IKEd5woHl9hGBU0qCAfrQ4+7Tqej0HzDzGQeXzS8tg9gq84x1qUdRvFk5BXbhuAyvJliY9F1/I07d2X0A=="], + "@react-navigation/native-stack": ["@react-navigation/native-stack@7.14.11", "", { "dependencies": { "@react-navigation/elements": "^2.9.14", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-1ufBtJ7KbVFlQhXsYSYHqjgkmP30AzJSgW48YjWMQZ3NZGAyYe34w9Wd4KpdebQCfDClPe9maU+8crA/awa6lQ=="], "@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="], @@ -416,8 +414,6 @@ "@svgr/plugin-svgo": ["@svgr/plugin-svgo@8.1.0", "", { "dependencies": { "cosmiconfig": "^8.1.3", "deepmerge": "^4.3.1", "svgo": "^3.0.2" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA=="], - "@trysound/sax": ["@trysound/sax@0.2.0", "", {}, "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="], - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -434,7 +430,7 @@ "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], - "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], @@ -454,7 +450,7 @@ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], @@ -484,11 +480,11 @@ "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="], - "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="], + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="], - "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ=="], + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="], - "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="], + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="], "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.0", "", { "dependencies": { "hermes-parser": "0.32.0" } }, "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg=="], @@ -502,7 +498,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ=="], "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], @@ -510,11 +506,11 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], @@ -532,7 +528,7 @@ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001788", "", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -576,9 +572,9 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="], + "core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="], - "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + "cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -592,7 +588,7 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -624,7 +620,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], + "electron-to-chromium": ["electron-to-chromium@1.5.342", "", {}, "sha512-GTuy59SdGxYgz+HN8KwOjFAVF2gfoKEmv0PFholcvVtbI9GPDND0m6ynGX3gAKOavcHRLrcfNy0QMbHbAemYdw=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -672,7 +668,7 @@ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - "fast-xml-parser": ["fast-xml-parser@4.5.3", "", { "dependencies": { "strnum": "^1.1.1" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig=="], + "fast-xml-parser": ["fast-xml-parser@4.5.6", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A=="], "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], @@ -726,7 +722,7 @@ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], "hermes-compiler": ["hermes-compiler@250829098.0.7", "", {}, "sha512-8QOmg1VjAWv8poFVslJDY8qkvjTy/UiO3R/hyGoC0IAchLzBdS9/TmAvI9cN1F3yLTEjimAIQQtUslpBMPXVVg=="], @@ -822,7 +818,7 @@ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "launch-editor": ["launch-editor@2.12.0", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg=="], + "launch-editor": ["launch-editor@2.13.2", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg=="], "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], @@ -864,45 +860,45 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@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", "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.32.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.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="], + "metro": ["metro@0.83.6", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", "@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", "chalk": "^4.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.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.83.6", "metro-cache": "0.83.6", "metro-cache-key": "0.83.6", "metro-config": "0.83.6", "metro-core": "0.83.6", "metro-file-map": "0.83.6", "metro-resolver": "0.83.6", "metro-runtime": "0.83.6", "metro-source-map": "0.83.6", "metro-symbolicate": "0.83.6", "metro-transform-plugins": "0.83.6", "metro-transform-worker": "0.83.6", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-pbdndsAZ2F/ceopDdhVbttpa/hfLzXPJ/husc+QvQ33R0D9UXJKzTn5+OzOXx4bpQNtAKF2bY88cCI3Zl44xDQ=="], - "metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="], + "metro-babel-transformer": ["metro-babel-transformer@0.83.6", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.35.0", "metro-cache-key": "0.83.6", "nullthrows": "^1.1.1" } }, "sha512-1AnuazBpzY3meRMr04WUw14kRBkV0W3Ez+AA75FAeNpRyWNN5S3M3PHLUbZw7IXq7ZeOzceyRsHStaFrnWd+8w=="], - "metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="], + "metro-cache": ["metro-cache@0.83.6", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.6" } }, "sha512-DpvZE32feNkqfZkI4Fic7YI/Kw8QP9wdl1rC4YKPrA77wQbI9vXbxjmfkCT/EGwBTFOPKqvIXo+H3BNe93YyiQ=="], - "metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="], + "metro-cache-key": ["metro-cache-key@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-5gdK4PVpgNOHi7xCGrgesNP1AuOA2TiPqpcirGXZi4RLLzX1VMowpkgTVtBfpQQCqWoosQF9yrSo9/KDQg1eBg=="], - "metro-config": ["metro-config@0.83.3", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.3", "metro-cache": "0.83.3", "metro-core": "0.83.3", "metro-runtime": "0.83.3", "yaml": "^2.6.1" } }, "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA=="], + "metro-config": ["metro-config@0.83.6", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.6", "metro-cache": "0.83.6", "metro-core": "0.83.6", "metro-runtime": "0.83.6", "yaml": "^2.6.1" } }, "sha512-G5622400uNtnAMlppEA5zkFAZltEf7DSGhOu09BkisCxOlVMWfdosD/oPyh4f2YVQsc1MBYyp4w6OzbExTYarg=="], - "metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="], + "metro-core": ["metro-core@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.6" } }, "sha512-l+yQ2fuIgR//wszUlMrrAa9+Z+kbKazd0QOh0VQY7jC4ghb7yZBBSla/UMYRBZZ6fPg9IM+wD3+h+37a5f9etw=="], - "metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="], + "metro-file-map": ["metro-file-map@0.83.6", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-Jg3oN604C7GWbQwFAUXt8KsbMXeKfsxbZ5HFy4XFM3ggTS+ja9QgUmq9B613kgXv3G4M6rwiI6cvh9TRly4x3w=="], - "metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="], + "metro-minify-terser": ["metro-minify-terser@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-Vx3/Ne9Q+EIEDLfKzZUOtn/rxSNa/QjlYxc42nvK4Mg8mB6XUgd3LXX5ZZVq7lzQgehgEqLrbgShJPGfeF8PnQ=="], - "metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], + "metro-resolver": ["metro-resolver@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-lAwR/FsT1uJ5iCt4AIsN3boKfJ88aN8bjvDT5FwBS0tKeKw4/sbdSTWlFxc7W/MUTN5RekJ3nQkJRIWsvs28tA=="], - "metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="], + "metro-runtime": ["metro-runtime@0.83.6", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-WQPua1G2VgYbwRn6vSKxOhTX7CFbSf/JdUu6Nd8bZnPXckOf7HQ2y51NXNQHoEsiuawathrkzL8pBhv+zgZFmg=="], - "metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="], + "metro-source-map": ["metro-source-map@0.83.6", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.6", "nullthrows": "^1.1.1", "ob1": "0.83.6", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-AqJbOMMpeyyM4iNI91pchqDIszzNuuHApEhg6OABqZ+9mjLEqzcIEQ/fboZ7x74fNU5DBd2K36FdUQYPqlGClA=="], - "metro-symbolicate": ["metro-symbolicate@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw=="], + "metro-symbolicate": ["metro-symbolicate@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.6", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-4nvkmv9T7ozhprlPwk/+xm0SVPsxly5kYyMHdNaOlFemFz4df9BanvD46Ac6OISu/4Idinzfk2KVb++6OfzPAQ=="], - "metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="], + "metro-transform-plugins": ["metro-transform-plugins@0.83.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@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" } }, "sha512-V+zoY2Ul0v0BW6IokJkTud3raXmDdbdwkUQ/5eiSoy0jKuKMhrDjdH+H5buCS5iiJdNbykOn69Eip+Sqymkodg=="], - "metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="], + "metro-transform-worker": ["metro-transform-worker@0.83.6", "", { "dependencies": { "@babel/core": "^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.83.6", "metro-babel-transformer": "0.83.6", "metro-cache": "0.83.6", "metro-cache-key": "0.83.6", "metro-minify-terser": "0.83.6", "metro-source-map": "0.83.6", "metro-transform-plugins": "0.83.6", "nullthrows": "^1.1.1" } }, "sha512-G5kDJ/P0ZTIf57t3iyAd5qIXbj2Wb1j7WtIDh82uTFQHe2Mq2SO9aXG9j1wI+kxZlIe58Z22XEXIKMl89z0ibQ=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], @@ -918,7 +914,7 @@ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], "node-stream-zip": ["node-stream-zip@1.15.0", "", {}, "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="], @@ -930,7 +926,7 @@ "nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="], - "ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="], + "ob1": ["ob1@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-m/xZYkwcjo6UqLMrUICEB3iHk7Bjt3RSR7KXMi6Y1MO/kGkPhoRmfUDF6KAan3rLAZ7ABRqnQyKUTwaqZgUV4w=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -974,7 +970,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], @@ -1004,19 +1000,19 @@ "react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="], - "react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="], + "react-is": ["react-is@19.2.5", "", {}, "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ=="], "react-native": ["react-native@0.84.0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.84.0", "@react-native/codegen": "0.84.0", "@react-native/community-cli-plugin": "0.84.0", "@react-native/gradle-plugin": "0.84.0", "@react-native/js-polyfills": "0.84.0", "@react-native/normalize-colors": "0.84.0", "@react-native/virtualized-lists": "0.84.0", "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.32.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "hermes-compiler": "250829098.0.7", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.3", "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.2.3" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-CcBfucLDHz8MAjQx9kFXasYtpcn8zP1YapUgGtAy0psRZTLShwF9yeh5+ErSgEK2gXV1CCSz7hqCZqx1eMyBLA=="], "react-native-dotenv": ["react-native-dotenv@3.4.11", "", { "dependencies": { "dotenv": "^16.4.5" }, "peerDependencies": { "@babel/runtime": "^7.20.6" } }, "sha512-6vnIE+WHABSeHCaYP6l3O1BOEhWxKH6nHAdV7n/wKn/sciZ64zPPp2NUdEUf1m7g4uuzlLbjgr+6uDt89q2DOg=="], - "react-native-onesignal": ["react-native-onesignal@../../react-native-onesignal.tgz", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react-native": ">=0.76.0" } }, "sha512-gc/KSxGWHWszNWwjZXzRoP3iHiWO5ucpG1Uyj+Y/qiAfU44eXznmHzYZTb61sAJxPakI+yKd3dhAHhbjolGB1g=="], + "react-native-onesignal": ["react-native-onesignal@../../react-native-onesignal.tgz", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react-native": ">=0.76.0" } }, "sha512-waBo86i8QAv33r75UG6X1k9z6GA2sTxjt0hgf2V25z7IVcqhUUbp5sbH+pzYPBAA/yUQGgb6Im5GUvbNH3zRBg=="], - "react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="], + "react-native-safe-area-context": ["react-native-safe-area-context@5.7.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ=="], - "react-native-screens": ["react-native-screens@4.23.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw=="], + "react-native-screens": ["react-native-screens@4.24.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA=="], - "react-native-svg": ["react-native-svg@15.15.3", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA=="], + "react-native-svg": ["react-native-svg@15.15.4", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A=="], "react-native-svg-transformer": ["react-native-svg-transformer@1.5.3", "", { "dependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "path-dirname": "^1.0.2" }, "peerDependencies": { "react-native": ">=0.59.0", "react-native-svg": ">=12.0.0" } }, "sha512-M4uFg5pUt35OMgjD4rWWbwd6PmxV96W7r/gQTTa+iZA5B+jO6aURhzAZGLHSrg1Kb91cKG0Rildy9q1WJvYstg=="], @@ -1038,13 +1034,13 @@ "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="], - "regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + "regjsparser": ["regjsparser@0.13.1", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -1060,6 +1056,8 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1084,7 +1082,7 @@ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], @@ -1138,15 +1136,15 @@ "svg-parser": ["svg-parser@2.0.4", "", {}, "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="], - "svgo": ["svgo@3.3.2", "", { "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0" }, "bin": "./bin/svgo" }, "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw=="], + "svgo": ["svgo@3.3.3", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng=="], - "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="], + "terser": ["terser@5.46.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ=="], "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], "throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="], - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], @@ -1164,7 +1162,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], @@ -1216,7 +1214,7 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -1250,11 +1248,13 @@ "@svgr/plugin-svgo/cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], + "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "ansi-fragments/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -1284,7 +1284,7 @@ "jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], - "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "jest-util/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], @@ -1292,7 +1292,13 @@ "logkitty/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "metro/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "metro/hermes-parser": ["hermes-parser@0.35.0", "", { "dependencies": { "hermes-estree": "0.35.0" } }, "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA=="], + + "metro-babel-transformer/hermes-parser": ["hermes-parser@0.35.0", "", { "dependencies": { "hermes-estree": "0.35.0" } }, "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], @@ -1322,6 +1328,8 @@ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -1330,6 +1338,8 @@ "@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "ansi-fragments/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -1352,6 +1362,12 @@ "logkitty/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.35.0", "", {}, "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg=="], + + "metro/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "metro/hermes-parser/hermes-estree": ["hermes-estree@0.35.0", "", {}, "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg=="], + "react-native-vector-icons/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], "react-native-vector-icons/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], @@ -1362,6 +1378,8 @@ "svgo/css-tree/mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="], + "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "logkitty/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], diff --git a/examples/demo/ios/ExportOptions.plist b/examples/demo/ios/ExportOptions.plist new file mode 100644 index 00000000..d4191efe --- /dev/null +++ b/examples/demo/ios/ExportOptions.plist @@ -0,0 +1,23 @@ + + + + + method + development + teamID + 99SW8E36CT + signingStyle + manual + stripSwiftSymbols + + provisioningProfiles + + com.onesignal.example + Appium OneSignal Main + com.onesignal.example.NSE + Appium OneSignal NSE RN + com.onesignal.example.LA + Appium OneSignal Widget RN + + + diff --git a/examples/demo/ios/Podfile b/examples/demo/ios/Podfile index b2e76e49..6e8a662b 100644 --- a/examples/demo/ios/Podfile +++ b/examples/demo/ios/Podfile @@ -38,7 +38,8 @@ target 'demo' do installer, config[:reactNativePath], :mac_catalyst_enabled => false, - # :ccache_enabled => true + # ccache doesn't work with Xcode's explicit modules feature + :ccache_enabled => false ) end end diff --git a/examples/demo/ios/Podfile.lock b/examples/demo/ios/Podfile.lock index fb49e9c6..7cfcd949 100644 --- a/examples/demo/ios/Podfile.lock +++ b/examples/demo/ios/Podfile.lock @@ -1446,7 +1446,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - ReactNativeDependencies - - react-native-onesignal (5.4.2): + - react-native-onesignal (5.4.3): - hermes-engine - OneSignalXCFramework (= 5.5.0) - RCTRequired @@ -1469,7 +1469,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context (5.6.2): + - react-native-safe-area-context (5.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1481,8 +1481,8 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.6.2) - - react-native-safe-area-context/fabric (= 5.6.2) + - react-native-safe-area-context/common (= 5.7.0) + - react-native-safe-area-context/fabric (= 5.7.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1493,7 +1493,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/common (5.6.2): + - react-native-safe-area-context/common (5.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1515,7 +1515,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/fabric (5.6.2): + - react-native-safe-area-context/fabric (5.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1945,7 +1945,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNScreens (4.23.0): + - RNScreens (4.24.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1967,9 +1967,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNScreens/common (= 4.23.0) + - RNScreens/common (= 4.24.0) - Yoga - - RNScreens/common (4.23.0): + - RNScreens/common (4.24.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1992,7 +1992,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNSVG (15.15.3): + - RNSVG (15.15.4): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2013,9 +2013,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNSVG/common (= 15.15.3) + - RNSVG/common (= 15.15.4) - Yoga - - RNSVG/common (15.15.3): + - RNSVG/common (15.15.4): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2311,7 +2311,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: c12d2108050e27952983d565a232f6f7b1ad5e69 - hermes-engine: af765795aea407566349648840252cf0a57e5da2 + hermes-engine: 177322198264d50dc281084c48e1ed80ea14884c OneSignalXCFramework: 943852e7d70d719f73e9669d48620aeec1b93022 RCTDeprecation: 3280799c14232a56e5a44f92981a8ee33bc69fd9 RCTRequired: 9854a51b0f65ccf43ea0b744df4d70fce339db32 @@ -2321,7 +2321,7 @@ SPEC CHECKSUMS: React: 7ef36630d07638043a134a7dd2ec17e0be10fc3c React-callinvoker: af4e8fe1d60ab63dd8d74c2a68988064c2848954 React-Core: c609976c034ba9556bef9850a571a71bd458d73f - React-Core-prebuilt: bebfbabeafa809729563853ec559bf7fbd74afb3 + React-Core-prebuilt: f4df078355bf9605a0d0278aa5c63fcd4b764a1d React-CoreModules: 0ea85f3b3f4b8cbfb3afacd2ed85458fb878517a React-cxxreact: 6752bab77c0599d6136e2b8b9b64b4a7d316d401 React-debug: 38389b86e3570558ec73dd4cbc0cd2f2eec47a51 @@ -2349,8 +2349,8 @@ SPEC CHECKSUMS: React-logger: 9e51e01455f15cb3ef87a09a1ec773cdb22d56c1 React-Mapbuffer: 92b99e450e8ff598b27d6e4db3a75e04fd45e9a9 React-microtasksnativemodule: 2fe0f2bd2840dedbd66c0ac249c64f977f39cc18 - react-native-onesignal: 426b38a83b1f4f60d540c3c240dfa2e64dc10520 - react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2 + react-native-onesignal: dc0212667c92798d90a72316aec304f9df6dddc4 + react-native-safe-area-context: ae7587b95fb580d1800c5b0b2a7bd48c2868e67a React-NativeModulesApple: 44a9474594566cd03659f92e38f42599c6b9dee4 React-networking: db73d91466cb134fcbdaaa579fb2de14e2c2ea01 React-oscompat: b924b8609d06899f00ab1aa813b0cde9c5e12771 @@ -2384,13 +2384,13 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 6b7e8d8d974ed13fb66698d82c30c5e70c1f7d3a ReactCodegen: c5e5343f6691b0cd76913b9be5e89e5a83ff3315 ReactCommon: 92b53b0bd7f7d86154dc9f512c1ea5dee717cc72 - ReactNativeDependencies: 0b2cb4c4d6935ab9fa9cec0f764d27005ec271cc + ReactNativeDependencies: eaebe8c9533ec2c41b08a02844af461bfff10540 RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 - RNScreens: 14243fa0d9842ffa7f8bb2d00b6c3cfd3ca817e8 - RNSVG: 507bf2685de6b3d49449efd4aae7e7471bb9c433 + RNScreens: 6cb648bdad8fe9bee9259fe144df95b6d1d5b707 + RNSVG: c69f7709226108f5eb89b5aa8833c17a36345468 RNVectorIcons: 4351544f100d4f12cac156a7c13399e60bab3e26 Yoga: 772166513f9cd2d61a6251d0dacbbfaa5b537479 -PODFILE CHECKSUM: e8f456eb1aacc34abb413d1757b2f6bdc599bace +PODFILE CHECKSUM: f62fbda480f1d76e1eac7d980b0960570d6cfe89 COCOAPODS: 1.16.2 diff --git a/examples/demo/ios/demo.xcodeproj/project.pbxproj b/examples/demo/ios/demo.xcodeproj/project.pbxproj index cd493ce5..a4484923 100644 --- a/examples/demo/ios/demo.xcodeproj/project.pbxproj +++ b/examples/demo/ios/demo.xcodeproj/project.pbxproj @@ -366,10 +366,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo/Pods-demo-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo/Pods-demo-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-demo/Pods-demo-frameworks.sh\"\n"; @@ -427,10 +431,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo/Pods-demo-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo/Pods-demo-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-demo/Pods-demo-resources.sh\"\n"; @@ -568,6 +576,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CC = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang.sh"; + CCACHE_BINARY = ccache; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; CLANG_CXX_LIBRARY = "libc++"; @@ -595,6 +605,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; @@ -614,6 +625,8 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang.sh"; + LDPLUSPLUS = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -650,6 +663,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CC = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang.sh"; + CCACHE_BINARY = ccache; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; CLANG_CXX_LIBRARY = "libc++"; @@ -677,6 +692,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; @@ -689,6 +705,8 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang.sh"; + LDPLUSPLUS = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -735,7 +753,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalNotificationServiceExtensionRN; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.NSE; PRODUCT_NAME = OneSignalNotificationServiceExtension; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -759,7 +777,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalNotificationServiceExtensionRN; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.NSE; PRODUCT_NAME = OneSignalNotificationServiceExtension; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -782,7 +800,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalWidgetExtension; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.LA; PRODUCT_NAME = OneSignalWidgetExtension; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -805,7 +823,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.OneSignalWidgetExtension; + PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.example.LA; PRODUCT_NAME = OneSignalWidgetExtension; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; diff --git a/examples/demo/package.json b/examples/demo/package.json index 5317f912..d49b88a7 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -8,7 +8,8 @@ "preios": "bun run setup", "android": "bash ../run-android.sh", "ios": "bash ../run-ios.sh", - "clean:android": "rm -rf android/app/build android/app/.cxx android/build", + "update:pods": "cd ios && pod update OneSignalXCFramework --no-repo-update && cd ..", + "clean:android": "rm -rf android/app/build android/app/.cxx android/build && adb uninstall com.onesignal.example >/dev/null 2>&1 || true", "clean:ios": "rm -rf ios/build ios/Pods", "start": "react-native start" }, diff --git a/examples/demo/src/components/AppHeader.tsx b/examples/demo/src/components/AppHeader.tsx new file mode 100644 index 00000000..82c9e52f --- /dev/null +++ b/examples/demo/src/components/AppHeader.tsx @@ -0,0 +1,89 @@ +import { useNavigation } from '@react-navigation/native'; +import type { NativeStackHeaderProps } from '@react-navigation/native-stack'; +import React from 'react'; +import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import Icon from 'react-native-vector-icons/MaterialIcons'; + +import { AppColors } from '../theme'; + +export default function AppHeader({ options, back }: NativeStackHeaderProps) { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + const titleNode = + typeof options.headerTitle === 'function' ? ( + options.headerTitle({ children: options.title ?? '', tintColor: AppColors.white }) + ) : ( + {options.title ?? ''} + ); + + return ( + + + + {back ? ( + [styles.backBtn, pressed && styles.pressed]} + > + + + ) : null} + + {titleNode} + + + + ); +} + +const styles = StyleSheet.create({ + shadowWrap: { + backgroundColor: AppColors.osPrimary, + ...Platform.select({ + ios: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.25, + shadowRadius: 6, + }, + android: { + elevation: 8, + }, + }), + zIndex: 10, + }, + bar: { + height: 56, + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 4, + }, + side: { + width: 56, + alignItems: 'center', + justifyContent: 'center', + }, + center: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + backBtn: { + width: 40, + height: 40, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 20, + }, + pressed: { + opacity: 0.6, + }, + title: { + color: AppColors.white, + fontSize: 18, + fontWeight: '600', + }, +}); diff --git a/examples/demo/src/components/ListWidgets.tsx b/examples/demo/src/components/ListWidgets.tsx index 462ccde6..edd0e6c7 100644 --- a/examples/demo/src/components/ListWidgets.tsx +++ b/examples/demo/src/components/ListWidgets.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { ActivityIndicator, View, Text, TouchableOpacity, StyleSheet } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialIcons'; import { AppColors, AppTextStyles, AppTheme } from '../theme'; @@ -10,7 +10,7 @@ interface PairItemProps { itemValue: string; layout?: 'inline' | 'stacked'; onDelete?: () => void; - testID?: string; + sectionKey?: string; } export function PairItem({ @@ -18,32 +18,52 @@ export function PairItem({ itemValue, layout = 'inline', onDelete, - testID, + sectionKey, }: PairItemProps) { return ( - + {layout === 'stacked' ? ( - + {itemKey} - + {itemValue} ) : ( <> - + {itemKey} | - + {itemValue} )} {onDelete && ( - + )} @@ -55,17 +75,21 @@ export function PairItem({ interface SingleItemProps { value: string; onDelete?: () => void; - testID?: string; + sectionKey?: string; } -export function SingleItem({ value, onDelete, testID }: SingleItemProps) { +export function SingleItem({ value, onDelete, sectionKey }: SingleItemProps) { return ( - + {value} {onDelete && ( - + )} @@ -87,15 +111,35 @@ export function EmptyState({ message, testID }: EmptyStateProps) { ); } +// LoadingState - shown inside a list area while data is being fetched +interface LoadingStateProps { + testID?: string; +} + +export function LoadingState({ testID }: LoadingStateProps) { + return ( + + + + ); +} + // PairList (simple, no collapse) interface PairListProps { items: [string, string][]; layout?: 'inline' | 'stacked'; onDelete?: (key: string) => void; filterKeys?: string[]; + sectionKey?: string; } -export function PairList({ items, layout = 'inline', onDelete, filterKeys }: PairListProps) { +export function PairList({ + items, + layout = 'inline', + onDelete, + filterKeys, + sectionKey, +}: PairListProps) { const filtered = filterKeys ? items.filter(([k]) => !filterKeys.includes(k)) : items; if (filtered.length === 0) { @@ -112,7 +156,7 @@ export function PairList({ items, layout = 'inline', onDelete, filterKeys }: Pai itemValue={v} layout={layout} onDelete={onDelete ? () => onDelete(k) : undefined} - testID={`pair_item_${idx}`} + sectionKey={sectionKey} /> ))} @@ -127,12 +171,16 @@ interface CollapsibleSingleListProps { items: string[]; onDelete?: (value: string) => void; emptyMessage: string; + loading?: boolean; + sectionKey?: string; } export function CollapsibleSingleList({ items, onDelete, emptyMessage, + loading = false, + sectionKey, }: CollapsibleSingleListProps) { const [expanded, setExpanded] = useState(false); const showAll = expanded || items.length <= COLLAPSE_THRESHOLD; @@ -142,7 +190,14 @@ export function CollapsibleSingleList({ if (items.length === 0) { return ( - + {loading ? ( + + ) : ( + + )} ); } @@ -155,7 +210,7 @@ export function CollapsibleSingleList({ onDelete(item) : undefined} - testID={`single_item_${idx}`} + sectionKey={sectionKey} /> ))} diff --git a/examples/demo/src/components/LoadingOverlay.tsx b/examples/demo/src/components/LoadingOverlay.tsx deleted file mode 100644 index 7a888dce..00000000 --- a/examples/demo/src/components/LoadingOverlay.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { View, ActivityIndicator, StyleSheet } from 'react-native'; - -import { AppColors } from '../theme'; - -interface Props { - visible: boolean; -} - -export default function LoadingOverlay({ visible }: Props) { - if (!visible) { - return null; - } - return ( - - - - ); -} - -const styles = StyleSheet.create({ - overlay: { - ...StyleSheet.absoluteFillObject, - backgroundColor: AppColors.osBackdrop, - alignItems: 'center', - justifyContent: 'center', - zIndex: 999, - }, -}); diff --git a/examples/demo/src/components/LogView.tsx b/examples/demo/src/components/LogView.tsx deleted file mode 100644 index 99d27dde..00000000 --- a/examples/demo/src/components/LogView.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import Icon from 'react-native-vector-icons/MaterialIcons'; - -import LogManager, { LogEntry } from '../services/LogManager'; -import { AppColors, AppTextStyles } from '../theme'; - -const LEVEL_COLORS: Record = { - D: AppColors.osLogDebug, - I: AppColors.osLogInfo, - W: AppColors.osLogWarn, - E: AppColors.osLogError, -}; - -export default function LogView() { - const [entries, setEntries] = useState([]); - const [expanded, setExpanded] = useState(true); - const [containerWidth, setContainerWidth] = useState(0); - - useEffect(() => { - const unsub = LogManager.getInstance().subscribe((entry) => { - if (entry) { - setEntries((prev) => [entry, ...prev]); - } else { - setEntries([]); - } - }); - return unsub; - }, []); - - const clearLogs = () => LogManager.getInstance().clear(); - - return ( - - setExpanded((prev) => !prev)} - testID="log_view_header" - > - - LOGS - - ({entries.length}) - - - - {entries.length > 0 && ( - - - - )} - - - - - {expanded && ( - - setContainerWidth(e.nativeEvent.layout.width)} - > - - {entries.length === 0 ? ( - - No logs yet - - ) : ( - entries.map((entry, index) => ( - - - {entry.timestamp} - - - {entry.level} - - - {entry.tag}: {entry.message} - - - )) - )} - - - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - width: '100%', - backgroundColor: AppColors.osLogBackground, - }, - header: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 12, - }, - headerLeft: { - flexDirection: 'row', - alignItems: 'center', - }, - headerText: { - ...AppTextStyles.labelSmall, - fontWeight: '700', - color: AppColors.white, - letterSpacing: 0.8, - }, - countText: { - ...AppTextStyles.labelSmall, - color: AppColors.osGrey500, - marginLeft: 8, - }, - headerRight: { - flexDirection: 'row', - alignItems: 'center', - }, - expandIcon: { - marginLeft: 8, - }, - listContainer: { - height: 100, - }, - vertContent: { - paddingHorizontal: 12, - paddingBottom: 4, - }, - logRow: { - flexDirection: 'row', - paddingVertical: 1, - gap: 4, - }, - timestamp: { - ...AppTextStyles.labelSmall, - color: AppColors.osLogTimestamp, - fontFamily: 'monospace', - }, - level: { - ...AppTextStyles.labelSmall, - fontWeight: '700', - fontFamily: 'monospace', - }, - message: { - ...AppTextStyles.labelSmall, - color: AppColors.white, - fontFamily: 'monospace', - }, - emptyText: { - ...AppTextStyles.labelSmall, - color: AppColors.osGrey500, - paddingVertical: 12, - textAlign: 'center', - }, -}); diff --git a/examples/demo/src/components/SectionCard.tsx b/examples/demo/src/components/SectionCard.tsx index 5c2092c6..18f60ca9 100644 --- a/examples/demo/src/components/SectionCard.tsx +++ b/examples/demo/src/components/SectionCard.tsx @@ -7,16 +7,21 @@ interface Props { title: string; children?: React.ReactNode; onInfoTap?: () => void; + sectionKey?: string; style?: object; } -export default function SectionCard({ title, children, onInfoTap, style }: Props) { +export default function SectionCard({ title, children, onInfoTap, sectionKey, style }: Props) { return ( - + {title} {onInfoTap && ( - + )} diff --git a/examples/demo/src/components/modals/LoginModal.tsx b/examples/demo/src/components/modals/LoginModal.tsx index 937fbeff..b08fe244 100644 --- a/examples/demo/src/components/modals/LoginModal.tsx +++ b/examples/demo/src/components/modals/LoginModal.tsx @@ -22,12 +22,13 @@ export default function LoginModal({ visible, onConfirm, onClose }: Props) { const [userId, setUserId] = useState(''); const handleConfirm = () => { - if (!userId.trim()) { + const trimmed = userId.trim(); + if (!trimmed) { return; } - onConfirm(userId.trim()); setUserId(''); onClose(); + onConfirm(trimmed); }; const handleClose = () => { @@ -36,7 +37,7 @@ export default function LoginModal({ visible, onConfirm, onClose }: Props) { }; return ( - + updateRow(row.id, 'key', t)} autoFocus={idx === 0} {...AppInputProps} - testID={idx === 0 ? 'multi_pair_key_0' : undefined} + testID={`multipair_key_${idx}`} /> updateRow(row.id, 'value', t)} {...AppInputProps} - testID={idx === 0 ? 'multi_pair_value_0' : undefined} + testID={`multipair_value_${idx}`} /> {rows.length > 1 && ( ))} - - + Add Row + + + Add Row @@ -133,6 +139,7 @@ export default function MultiPairInputModal({ style={AppDialogStyles.actionBtn} onPress={handleConfirm} disabled={!allFilled} + testID="multipair_confirm_button" > { const isChecked = selected.has(key); return ( - toggle(key)}> + toggle(key)} + testID={`remove_checkbox_${key}`} + > void; keyTestID?: string; valueTestID?: string; + confirmTestID?: string; } export default function PairInputModal({ @@ -32,6 +33,7 @@ export default function PairInputModal({ onClose, keyTestID, valueTestID, + confirmTestID, }: Props) { const [keyValue, setKeyValue] = useState(''); const [val, setVal] = useState(''); @@ -91,6 +93,7 @@ export default function PairInputModal({ style={AppDialogStyles.actionBtn} onPress={handleConfirm} disabled={!canSubmit} + testID={confirmTestID} > - + - {tooltip.title} + + {tooltip.title} + - {tooltip.description} + + {tooltip.description} + {tooltip.options?.map((opt) => ( {opt.name} @@ -30,7 +39,7 @@ export default function TooltipModal({ visible, tooltip, onClose }: Props) { - Ok + OK diff --git a/examples/demo/src/components/modals/TrackEventModal.tsx b/examples/demo/src/components/modals/TrackEventModal.tsx index eac63317..09d1ad76 100644 --- a/examples/demo/src/components/modals/TrackEventModal.tsx +++ b/examples/demo/src/components/modals/TrackEventModal.tsx @@ -89,7 +89,7 @@ export default function TrackEventModal({ visible, onConfirm, onClose }: Props) onChangeText={setName} autoFocus {...AppInputProps} - testID="track_event_name_input" + testID="event_name_input" /> Properties (optional, JSON) {!!jsonError && {jsonError}} @@ -111,7 +111,7 @@ export default function TrackEventModal({ visible, onConfirm, onClose }: Props) style={AppDialogStyles.actionBtn} onPress={handleConfirm} disabled={!canSubmit} - testID="track_event_confirm_button" + testID="event_track_button" > void; onAddMultiple: (pairs: Record) => void; onInfoTap?: () => void; } -export default function AliasesSection({ aliases, onAdd, onAddMultiple, onInfoTap }: Props) { +export default function AliasesSection({ + aliases, + loading = false, + onAdd, + onAddMultiple, + onInfoTap, +}: Props) { const [addVisible, setAddVisible] = useState(false); const [addMultipleVisible, setAddMultipleVisible] = useState(false); const filtered = aliases.filter(([k]) => !FILTERED_KEYS.includes(k)); return ( - + {filtered.length === 0 ? ( - + {loading ? ( + + ) : ( + + )} ) : ( - + )} - setAddVisible(true)} testID="add_alias_button" /> setAddVisible(true)} + testID="add_alias_button" + /> + setAddMultipleVisible(true)} testID="add_multiple_aliases_button" /> @@ -49,6 +64,7 @@ export default function AliasesSection({ aliases, onAdd, onAddMultiple, onInfoTa onClose={() => setAddVisible(false)} keyTestID="alias_label_input" valueTestID="alias_id_input" + confirmTestID="alias_confirm_button" /> + {/* App ID display */} App ID - - {appId} + + {maskValue(appId)} diff --git a/examples/demo/src/components/sections/EmailsSection.tsx b/examples/demo/src/components/sections/EmailsSection.tsx index ee6806c5..46bebf0b 100644 --- a/examples/demo/src/components/sections/EmailsSection.tsx +++ b/examples/demo/src/components/sections/EmailsSection.tsx @@ -9,18 +9,31 @@ import SectionCard from '../SectionCard'; interface Props { emails: string[]; + loading?: boolean; onAdd: (email: string) => void; onRemove: (email: string) => void; onInfoTap?: () => void; } -export default function EmailsSection({ emails, onAdd, onRemove, onInfoTap }: Props) { +export default function EmailsSection({ + emails, + loading = false, + onAdd, + onRemove, + onInfoTap, +}: Props) { const [addVisible, setAddVisible] = useState(false); return ( - + - + + diff --git a/examples/demo/src/components/sections/LiveActivitySection.tsx b/examples/demo/src/components/sections/LiveActivitySection.tsx index 9079d2ff..0807d3c6 100644 --- a/examples/demo/src/components/sections/LiveActivitySection.tsx +++ b/examples/demo/src/components/sections/LiveActivitySection.tsx @@ -12,13 +12,20 @@ const ORDER_STATUSES = [ ]; interface Props { + hasApiKey: boolean; onStart: (activityId: string, attributes: object, content: object) => void; onUpdate: (activityId: string, eventUpdates: Record) => Promise; onEnd: (activityId: string) => Promise; onInfoTap?: () => void; } -export default function LiveActivitySection({ onStart, onUpdate, onEnd, onInfoTap }: Props) { +export default function LiveActivitySection({ + hasApiKey, + onStart, + onUpdate, + onEnd, + onInfoTap, +}: Props) { const [activityId, setActivityId] = useState('order-1'); const [orderNumber, setOrderNumber] = useState('ORD-1234'); const [statusIndex, setStatusIndex] = useState(0); @@ -58,7 +65,7 @@ export default function LiveActivitySection({ onStart, onUpdate, onEnd, onInfoTa const nextStatus = ORDER_STATUSES[(statusIndex + 1) % ORDER_STATUSES.length]; return ( - + - - {/* Requires API key */} - - {/* Requires API key */} onEnd(activityId)} - disabled={!activityId.trim()} + disabled={!activityId.trim() || !hasApiKey} variant="outlined" testID="end_live_activity_button" /> @@ -151,13 +154,6 @@ const styles = StyleSheet.create({ paddingVertical: 4, marginLeft: 8, }, - hint: { - ...AppTextStyles.bodyMedium, - color: AppColors.osGrey500, - fontSize: 12, - marginTop: 8, - marginBottom: 4, - }, buttons: { marginTop: AppSpacing.gap, }, diff --git a/examples/demo/src/components/sections/LocationSection.tsx b/examples/demo/src/components/sections/LocationSection.tsx index bc664dc7..0bc923fd 100644 --- a/examples/demo/src/components/sections/LocationSection.tsx +++ b/examples/demo/src/components/sections/LocationSection.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; import { AppTheme, AppSpacing } from '../../theme'; +import { showSnackbar } from '../../utils/showSnackbar'; import ActionButton from '../ActionButton'; import SectionCard from '../SectionCard'; import ToggleRow from '../ToggleRow'; @@ -9,6 +10,7 @@ import ToggleRow from '../ToggleRow'; interface Props { locationShared: boolean; onSetLocationShared: (value: boolean) => void; + onCheckLocationShared: () => Promise; onRequestLocationPermission: () => void; onInfoTap?: () => void; } @@ -16,11 +18,17 @@ interface Props { export default function LocationSection({ locationShared, onSetLocationShared, + onCheckLocationShared, onRequestLocationPermission, onInfoTap, }: Props) { + const handleCheckLocation = async () => { + const shared = await onCheckLocationShared(); + showSnackbar(`Location shared: ${shared}`); + }; + return ( - + + ); } diff --git a/examples/demo/src/components/sections/OutcomesSection.tsx b/examples/demo/src/components/sections/OutcomesSection.tsx index 6eda4644..4483352f 100644 --- a/examples/demo/src/components/sections/OutcomesSection.tsx +++ b/examples/demo/src/components/sections/OutcomesSection.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; +import { showSnackbar } from '../../utils/showSnackbar'; import ActionButton from '../ActionButton'; import OutcomeModal from '../modals/OutcomeModal'; import SectionCard from '../SectionCard'; @@ -19,8 +20,23 @@ export default function OutcomesSection({ }: Props) { const [modalVisible, setModalVisible] = useState(false); + const handleSendNormal = (name: string) => { + onSendNormal(name); + showSnackbar(`Outcome sent: ${name}`); + }; + + const handleSendUnique = (name: string) => { + onSendUnique(name); + showSnackbar(`Unique outcome sent: ${name}`); + }; + + const handleSendWithValue = (name: string, value: number) => { + onSendWithValue(name, value); + showSnackbar(`Outcome sent: ${name} = ${value}`); + }; + return ( - + setModalVisible(true)} @@ -28,9 +44,9 @@ export default function OutcomesSection({ /> setModalVisible(false)} /> diff --git a/examples/demo/src/components/sections/PushSection.tsx b/examples/demo/src/components/sections/PushSection.tsx index 1c7c6351..39e96019 100644 --- a/examples/demo/src/components/sections/PushSection.tsx +++ b/examples/demo/src/components/sections/PushSection.tsx @@ -1,3 +1,4 @@ +import { E2E_MODE } from '@env'; import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; @@ -6,6 +7,15 @@ import ActionButton from '../ActionButton'; import SectionCard from '../SectionCard'; import ToggleRow from '../ToggleRow'; +const MASK_CHAR = '•'; + +function maskValue(value: string): string { + if (E2E_MODE === 'true') { + return MASK_CHAR.repeat(value.length); + } + return value; +} + interface Props { pushSubscriptionId: string | undefined; isPushEnabled: boolean; @@ -24,12 +34,17 @@ export default function PushSection({ onInfoTap, }: Props) { return ( - + Push ID - - {pushSubscriptionId ?? '–'} + + {pushSubscriptionId ? maskValue(pushSubscriptionId) : '–'} diff --git a/examples/demo/src/components/sections/SendIamSection.tsx b/examples/demo/src/components/sections/SendIamSection.tsx index 0c810164..472bb080 100644 --- a/examples/demo/src/components/sections/SendIamSection.tsx +++ b/examples/demo/src/components/sections/SendIamSection.tsx @@ -20,7 +20,7 @@ const IAM_TYPES: InAppMessageType[] = [ export default function SendIamSection({ onSendIam, onInfoTap }: Props) { return ( - + {IAM_TYPES.map((type) => ( onSendIam(type)} activeOpacity={0.8} testID={`send_iam_${type}_button`} + accessibilityLabel={iamTypeLabel[type]} > diff --git a/examples/demo/src/components/sections/SendPushSection.tsx b/examples/demo/src/components/sections/SendPushSection.tsx index b5f89f40..db43642c 100644 --- a/examples/demo/src/components/sections/SendPushSection.tsx +++ b/examples/demo/src/components/sections/SendPushSection.tsx @@ -21,32 +21,32 @@ export default function SendPushSection({ const [customVisible, setCustomVisible] = useState(false); return ( - + onSendNotification(NotificationType.Simple)} - testID="send_simple_push_button" + testID="send_simple_button" /> onSendNotification(NotificationType.WithImage)} - testID="send_image_push_button" + testID="send_image_button" /> onSendNotification(NotificationType.WithSound)} - testID="send_sound_push_button" + testID="send_sound_button" /> setCustomVisible(true)} - testID="send_custom_push_button" + testID="send_custom_button" /> void; onRemove: (sms: string) => void; onInfoTap?: () => void; } -export default function SmsSection({ smsNumbers, onAdd, onRemove, onInfoTap }: Props) { +export default function SmsSection({ + smsNumbers, + loading = false, + onAdd, + onRemove, + onInfoTap, +}: Props) { const [addVisible, setAddVisible] = useState(false); return ( - + - + setAddVisible(true)} testID="add_sms_button" /> void; onAddMultiple: (pairs: Record) => void; onRemoveSelected: (keys: string[]) => void; @@ -19,6 +20,7 @@ interface Props { export default function TagsSection({ tags, + loading = false, onAdd, onAddMultiple, onRemoveSelected, @@ -29,25 +31,34 @@ export default function TagsSection({ const [removeVisible, setRemoveVisible] = useState(false); return ( - + {tags.length === 0 ? ( - + {loading ? ( + + ) : ( + + )} ) : ( - onRemoveSelected([key])} /> + onRemoveSelected([key])} + sectionKey="tags" + /> )} - setAddVisible(true)} testID="add_tag_button" /> + setAddVisible(true)} testID="add_tag_button" /> setAddMultipleVisible(true)} testID="add_multiple_tags_button" /> {tags.length > 0 && ( setRemoveVisible(true)} variant="outlined" testID="remove_tags_button" @@ -62,6 +73,7 @@ export default function TagsSection({ onClose={() => setAddVisible(false)} keyTestID="tag_key_input" valueTestID="tag_value_input" + confirmTestID="tag_confirm_button" /> + setModalVisible(true)} @@ -23,6 +24,7 @@ export default function TrackEventSection({ onTrackEvent, onInfoTap }: Props) { visible={modalVisible} onConfirm={(name, properties) => { onTrackEvent(name, properties); + showSnackbar(`Event tracked: ${name}`); setModalVisible(false); }} onClose={() => setModalVisible(false)} diff --git a/examples/demo/src/components/sections/TriggersSection.tsx b/examples/demo/src/components/sections/TriggersSection.tsx index e66bf57a..e88b5041 100644 --- a/examples/demo/src/components/sections/TriggersSection.tsx +++ b/examples/demo/src/components/sections/TriggersSection.tsx @@ -9,12 +9,6 @@ import MultiSelectRemoveModal from '../modals/MultiSelectRemoveModal'; import PairInputModal from '../modals/PairInputModal'; import SectionCard from '../SectionCard'; -const styles = StyleSheet.create({ - listCard: { - marginBottom: AppSpacing.gap, - }, -}); - interface Props { triggers: [string, string][]; onAdd: (key: string, value: string) => void; @@ -37,32 +31,41 @@ export default function TriggersSection({ const [removeVisible, setRemoveVisible] = useState(false); return ( - + {triggers.length === 0 ? ( ) : ( - onRemoveSelected([key])} /> + onRemoveSelected([key])} + sectionKey="triggers" + /> )} - setAddVisible(true)} testID="add_trigger_button" /> setAddVisible(true)} + testID="add_trigger_button" + /> + setAddMultipleVisible(true)} testID="add_multiple_triggers_button" /> {triggers.length > 0 && ( <> setRemoveVisible(true)} variant="outlined" testID="remove_triggers_button" /> setAddVisible(false)} keyTestID="trigger_key_input" valueTestID="trigger_value_input" + confirmTestID="trigger_confirm_button" /> ); } + +const styles = StyleSheet.create({ + listCard: { + marginBottom: AppSpacing.gap, + }, +}); diff --git a/examples/demo/src/components/sections/UserSection.tsx b/examples/demo/src/components/sections/UserSection.tsx index 77af0f5c..0956ae7e 100644 --- a/examples/demo/src/components/sections/UserSection.tsx +++ b/examples/demo/src/components/sections/UserSection.tsx @@ -2,33 +2,55 @@ import React, { useState } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { AppColors, AppTextStyles, AppTheme, AppSpacing } from '../../theme'; +import { showSnackbar } from '../../utils/showSnackbar'; import ActionButton from '../ActionButton'; import LoginModal from '../modals/LoginModal'; import SectionCard from '../SectionCard'; interface Props { externalUserId: string | undefined; - onLogin: (externalUserId: string) => void; - onLogout: () => void; + onLogin: (externalUserId: string) => Promise; + onLogout: () => Promise; } export default function UserSection({ externalUserId, onLogin, onLogout }: Props) { const [loginVisible, setLoginVisible] = useState(false); const isLoggedIn = !!externalUserId; + const handleLogin = async (userId: string) => { + try { + await onLogin(userId); + showSnackbar(`Logged in as ${userId}`); + } catch (err) { + showSnackbar(`Login failed: ${String(err)}`); + } + }; + + const handleLogout = async () => { + try { + await onLogout(); + showSnackbar('User logged out'); + } catch (err) { + showSnackbar(`Logout failed: ${String(err)}`); + } + }; + return ( - + Status - + {isLoggedIn ? 'Logged In' : 'Anonymous'} External ID - + {externalUserId ?? '–'} @@ -41,14 +63,14 @@ export default function UserSection({ externalUserId, onLogin, onLogout }: Props {isLoggedIn && ( )} setLoginVisible(false)} /> diff --git a/examples/demo/src/context/AppContext.tsx b/examples/demo/src/context/AppContext.tsx deleted file mode 100644 index 32951cb9..00000000 --- a/examples/demo/src/context/AppContext.tsx +++ /dev/null @@ -1,801 +0,0 @@ -import React, { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useReducer, - useRef, -} from 'react'; -import { OneSignal } from 'react-native-onesignal'; -import Toast from 'react-native-toast-message'; - -import { NotificationType } from '../models/NotificationType'; -import OneSignalRepository from '../repositories/OneSignalRepository'; -import LogManager from '../services/LogManager'; -import OneSignalApiService from '../services/OneSignalApiService'; -import PreferencesService from '../services/PreferencesService'; - -const TAG = 'AppContext'; -const log = LogManager.getInstance(); -const apiService = OneSignalApiService.getInstance(); -const repository = new OneSignalRepository(apiService); -const preferences = PreferencesService.getInstance(); - -export interface AppState { - appId: string; - consentRequired: boolean; - privacyConsentGiven: boolean; - externalUserId: string | undefined; - pushSubscriptionId: string | undefined; - isPushEnabled: boolean; - hasNotificationPermission: boolean; - inAppMessagesPaused: boolean; - locationShared: boolean; - aliasesList: [string, string][]; - emailsList: string[]; - smsNumbersList: string[]; - tagsList: [string, string][]; - triggersList: [string, string][]; - isLoading: boolean; -} - -const initialState: AppState = { - appId: '77e32082-ea27-42e3-a898-c72e141824ef', - consentRequired: false, - privacyConsentGiven: false, - externalUserId: undefined, - pushSubscriptionId: undefined, - isPushEnabled: false, - hasNotificationPermission: false, - inAppMessagesPaused: false, - locationShared: false, - aliasesList: [], - emailsList: [], - smsNumbersList: [], - tagsList: [], - triggersList: [], - isLoading: false, -}; - -type UserDataPayload = { - aliasesList: [string, string][]; - tagsList: [string, string][]; - emailsList: string[]; - smsNumbersList: string[]; - externalUserId: string | undefined; -}; - -type AppAction = - | { type: 'SET_LOADING'; payload: boolean } - | { - type: 'SET_INITIAL_STATE'; - payload: { - appId: string; - consentRequired: boolean; - privacyConsentGiven: boolean; - inAppMessagesPaused: boolean; - locationShared: boolean; - externalUserId: string | undefined; - pushSubscriptionId: string | undefined; - isPushEnabled: boolean; - hasNotificationPermission: boolean; - }; - } - | { type: 'SET_USER_DATA'; payload: UserDataPayload } - | { type: 'CLEAR_USER_DATA' } - | { - type: 'SET_PUSH_SUBSCRIPTION'; - payload: { id: string | undefined; optedIn: boolean }; - } - | { type: 'SET_HAS_NOTIFICATION_PERMISSION'; payload: boolean } - | { type: 'SET_CONSENT_REQUIRED'; payload: boolean } - | { type: 'SET_PRIVACY_CONSENT_GIVEN'; payload: boolean } - | { type: 'SET_PUSH_ENABLED'; payload: boolean } - | { type: 'SET_IAM_PAUSED'; payload: boolean } - | { type: 'SET_LOCATION_SHARED'; payload: boolean } - | { type: 'ADD_ALIAS'; payload: { label: string; id: string } } - | { type: 'ADD_ALIASES'; payload: [string, string][] } - | { type: 'ADD_EMAIL'; payload: string } - | { type: 'REMOVE_EMAIL'; payload: string } - | { type: 'ADD_SMS'; payload: string } - | { type: 'REMOVE_SMS'; payload: string } - | { type: 'ADD_TAG'; payload: { key: string; value: string } } - | { type: 'ADD_TAGS'; payload: [string, string][] } - | { type: 'REMOVE_SELECTED_TAGS'; payload: string[] } - | { type: 'ADD_TRIGGER'; payload: { key: string; value: string } } - | { type: 'ADD_TRIGGERS'; payload: [string, string][] } - | { type: 'REMOVE_SELECTED_TRIGGERS'; payload: string[] } - | { type: 'CLEAR_TRIGGERS' } - | { type: 'LOGOUT' }; - -function appReducer(state: AppState, action: AppAction): AppState { - switch (action.type) { - case 'SET_LOADING': - return { ...state, isLoading: action.payload }; - case 'SET_INITIAL_STATE': - return { ...state, ...action.payload }; - case 'SET_USER_DATA': - return { ...state, ...action.payload, isLoading: false }; - case 'CLEAR_USER_DATA': - return { - ...state, - aliasesList: [], - emailsList: [], - smsNumbersList: [], - tagsList: [], - triggersList: [], - }; - case 'SET_PUSH_SUBSCRIPTION': - return { - ...state, - pushSubscriptionId: action.payload.id, - isPushEnabled: action.payload.optedIn, - }; - case 'SET_HAS_NOTIFICATION_PERMISSION': - return { ...state, hasNotificationPermission: action.payload }; - case 'SET_CONSENT_REQUIRED': - return { ...state, consentRequired: action.payload }; - case 'SET_PRIVACY_CONSENT_GIVEN': - return { ...state, privacyConsentGiven: action.payload }; - case 'SET_PUSH_ENABLED': - return { ...state, isPushEnabled: action.payload }; - case 'SET_IAM_PAUSED': - return { ...state, inAppMessagesPaused: action.payload }; - case 'SET_LOCATION_SHARED': - return { ...state, locationShared: action.payload }; - case 'ADD_ALIAS': - return { - ...state, - aliasesList: [...state.aliasesList, [action.payload.label, action.payload.id]], - }; - case 'ADD_ALIASES': - return { - ...state, - aliasesList: [...state.aliasesList, ...action.payload], - }; - case 'ADD_EMAIL': - return { ...state, emailsList: [...state.emailsList, action.payload] }; - case 'REMOVE_EMAIL': - return { - ...state, - emailsList: state.emailsList.filter((email) => email !== action.payload), - }; - case 'ADD_SMS': - return { - ...state, - smsNumbersList: [...state.smsNumbersList, action.payload], - }; - case 'REMOVE_SMS': - return { - ...state, - smsNumbersList: state.smsNumbersList.filter((sms) => sms !== action.payload), - }; - case 'ADD_TAG': { - const filtered = state.tagsList.filter(([key]) => key !== action.payload.key); - return { - ...state, - tagsList: [...filtered, [action.payload.key, action.payload.value]], - }; - } - case 'ADD_TAGS': { - const keys = new Set(action.payload.map(([key]) => key)); - return { - ...state, - tagsList: [...state.tagsList.filter(([key]) => !keys.has(key)), ...action.payload], - }; - } - case 'REMOVE_SELECTED_TAGS': { - const keys = new Set(action.payload); - return { - ...state, - tagsList: state.tagsList.filter(([key]) => !keys.has(key)), - }; - } - case 'ADD_TRIGGER': { - const filtered = state.triggersList.filter(([key]) => key !== action.payload.key); - return { - ...state, - triggersList: [...filtered, [action.payload.key, action.payload.value]], - }; - } - case 'ADD_TRIGGERS': { - const keys = new Set(action.payload.map(([key]) => key)); - return { - ...state, - triggersList: [...state.triggersList.filter(([key]) => !keys.has(key)), ...action.payload], - }; - } - case 'REMOVE_SELECTED_TRIGGERS': { - const keys = new Set(action.payload); - return { - ...state, - triggersList: state.triggersList.filter(([key]) => !keys.has(key)), - }; - } - case 'CLEAR_TRIGGERS': - return { ...state, triggersList: [] }; - case 'LOGOUT': - return { - ...state, - externalUserId: undefined, - aliasesList: [], - emailsList: [], - smsNumbersList: [], - tagsList: [], - triggersList: [], - isLoading: false, - }; - default: - return state; - } -} - -type AppContextValue = { - state: AppState; - loginUser: (externalUserId: string) => Promise; - logoutUser: () => Promise; - setConsentRequired: (required: boolean) => Promise; - setConsentGiven: (granted: boolean) => Promise; - promptPush: () => Promise; - setPushEnabled: (enabled: boolean) => void; - sendNotification: (type: NotificationType) => Promise; - sendCustomNotification: (title: string, body: string) => Promise; - clearAllNotifications: () => void; - setIamPaused: (paused: boolean) => Promise; - sendIamTrigger: (iamType: string) => void; - addAlias: (label: string, id: string) => void; - addAliases: (pairs: Record) => void; - addEmail: (email: string) => void; - removeEmail: (email: string) => void; - addSms: (sms: string) => void; - removeSms: (sms: string) => void; - addTag: (key: string, value: string) => void; - addTags: (pairs: Record) => void; - removeSelectedTags: (keys: string[]) => void; - sendOutcome: (name: string) => void; - sendUniqueOutcome: (name: string) => void; - sendOutcomeWithValue: (name: string, value: number) => void; - addTrigger: (key: string, value: string) => void; - addTriggers: (pairs: Record) => void; - removeSelectedTriggers: (keys: string[]) => void; - clearTriggers: () => void; - trackEvent: (name: string, properties?: Record) => void; - setLocationShared: (shared: boolean) => Promise; - requestLocationPermission: () => void; - startDefaultLiveActivity: (activityId: string, attributes: object, content: object) => void; - updateLiveActivity: (activityId: string, eventUpdates: Record) => Promise; - endLiveActivity: (activityId: string) => Promise; -}; - -const AppContext = createContext(null); - -interface Props { - children: React.ReactNode; -} - -function toPairs(pairs: Record): [string, string][] { - return Object.entries(pairs).map(([key, value]) => [key, value]); -} - -export function AppContextProvider({ children }: Props) { - const [state, dispatch] = useReducer(appReducer, initialState); - const mountedRef = useRef(true); - const requestSequenceRef = useRef(0); - - const fetchUserDataFromApi = useCallback(async () => { - const requestId = requestSequenceRef.current + 1; - requestSequenceRef.current = requestId; - - const onesignalId = await repository.getOnesignalId(); - if (!onesignalId) { - if (mountedRef.current && requestSequenceRef.current === requestId) { - dispatch({ type: 'SET_LOADING', payload: false }); - } - return; - } - - const userData = await repository.fetchUser(onesignalId); - if (!userData) { - if (mountedRef.current && requestSequenceRef.current === requestId) { - dispatch({ type: 'SET_LOADING', payload: false }); - } - return; - } - - const externalId = await repository.getExternalId(); - if (!mountedRef.current || requestSequenceRef.current !== requestId) { - return; - } - - dispatch({ - type: 'SET_USER_DATA', - payload: { - aliasesList: Object.entries(userData.aliases), - tagsList: Object.entries(userData.tags), - emailsList: userData.emails, - smsNumbersList: userData.smsNumbers, - externalUserId: externalId ?? userData.externalId, - }, - }); - }, []); - - useEffect(() => { - mountedRef.current = true; - return () => { - mountedRef.current = false; - }; - }, []); - - useEffect(() => { - const load = async () => { - const [appId, consentRequired, privacyConsentGiven, iamPaused, locationShared] = - await Promise.all([ - preferences.getAppId(), - preferences.getConsentRequired(), - preferences.getPrivacyConsent(), - preferences.getIamPaused(), - preferences.getLocationShared(), - ]); - - OneSignalApiService.getInstance().setAppId(appId); - - const externalId = await repository.getExternalId(); - const [pushId, pushOptedIn, hasPerm] = await Promise.all([ - repository.getPushSubscriptionIdAsync(), - repository.isPushOptedInAsync(), - repository.hasPermission(), - ]); - - if (!mountedRef.current) { - return; - } - - dispatch({ - type: 'SET_INITIAL_STATE', - payload: { - appId, - consentRequired, - privacyConsentGiven, - inAppMessagesPaused: iamPaused, - locationShared, - externalUserId: externalId, - pushSubscriptionId: pushId, - isPushEnabled: pushOptedIn, - hasNotificationPermission: hasPerm, - }, - }); - - const onesignalId = await repository.getOnesignalId(); - if (!mountedRef.current) { - return; - } - - if (onesignalId) { - dispatch({ type: 'SET_LOADING', payload: true }); - await fetchUserDataFromApi(); - } - }; - - load().catch((err) => { - log.e(TAG, `Initial load error: ${String(err)}`); - if (mountedRef.current) { - dispatch({ type: 'SET_LOADING', payload: false }); - } - }); - }, [fetchUserDataFromApi]); - - useEffect(() => { - const pushSubHandler = async () => { - if (!mountedRef.current) { - return; - } - const [id, optedIn] = await Promise.all([ - repository.getPushSubscriptionIdAsync(), - repository.isPushOptedInAsync(), - ]); - if (!mountedRef.current) { - return; - } - dispatch({ - type: 'SET_PUSH_SUBSCRIPTION', - payload: { - id, - optedIn, - }, - }); - }; - - const permissionHandler = async () => { - if (!mountedRef.current) { - return; - } - dispatch({ - type: 'SET_HAS_NOTIFICATION_PERMISSION', - payload: await repository.hasPermission(), - }); - }; - - const userChangeHandler = async () => { - log.i(TAG, 'User changed, fetching user data...'); - if (!mountedRef.current) { - return; - } - dispatch({ type: 'SET_LOADING', payload: true }); - await fetchUserDataFromApi(); - }; - - OneSignal.User.pushSubscription.addEventListener('change', pushSubHandler); - OneSignal.Notifications.addEventListener('permissionChange', permissionHandler); - OneSignal.User.addEventListener('change', userChangeHandler); - - return () => { - OneSignal.User.pushSubscription.removeEventListener('change', pushSubHandler); - OneSignal.Notifications.removeEventListener('permissionChange', permissionHandler); - OneSignal.User.removeEventListener('change', userChangeHandler); - }; - }, [fetchUserDataFromApi]); - - const loginUser = useCallback( - async (externalUserId: string) => { - dispatch({ type: 'SET_LOADING', payload: true }); - try { - repository.loginUser(externalUserId); - await preferences.setExternalUserId(externalUserId); - if (mountedRef.current) { - dispatch({ type: 'CLEAR_USER_DATA' }); - } - log.i(TAG, `Logged in as: ${externalUserId}`); - Toast.show({ type: 'info', text1: `Logged in as: ${externalUserId}` }); - await fetchUserDataFromApi(); - } catch (err) { - log.e(TAG, `Login error: ${String(err)}`); - if (mountedRef.current) { - dispatch({ type: 'SET_LOADING', payload: false }); - } - } - }, - [fetchUserDataFromApi], - ); - - const logoutUser = useCallback(async () => { - dispatch({ type: 'SET_LOADING', payload: true }); - repository.logoutUser(); - await preferences.setExternalUserId(null); - if (mountedRef.current) { - dispatch({ type: 'LOGOUT' }); - } - log.i(TAG, 'Logged out'); - Toast.show({ type: 'info', text1: 'Logged out' }); - }, []); - - const setConsentRequired = useCallback(async (required: boolean) => { - if (mountedRef.current) { - dispatch({ type: 'SET_CONSENT_REQUIRED', payload: required }); - } - repository.setConsentRequired(required); - await preferences.setConsentRequired(required); - }, []); - - const setConsentGiven = useCallback(async (granted: boolean) => { - if (mountedRef.current) { - dispatch({ type: 'SET_PRIVACY_CONSENT_GIVEN', payload: granted }); - } - repository.setConsentGiven(granted); - await preferences.setPrivacyConsent(granted); - }, []); - - const promptPush = useCallback(async () => { - const granted = await repository.requestPermission(true); - if (mountedRef.current) { - dispatch({ type: 'SET_HAS_NOTIFICATION_PERMISSION', payload: granted }); - } - }, []); - - const setPushEnabled = useCallback((enabled: boolean) => { - if (enabled) { - repository.optInPush(); - } else { - repository.optOutPush(); - } - dispatch({ type: 'SET_PUSH_ENABLED', payload: enabled }); - const msg = enabled ? 'Push enabled' : 'Push disabled'; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const sendNotification = useCallback(async (type: NotificationType) => { - const success = await repository.sendNotification(type); - const msg = success ? `Notification sent: ${type}` : 'Failed to send notification'; - log.i(TAG, msg); - Toast.show({ type: success ? 'info' : 'error', text1: msg }); - }, []); - - const sendCustomNotification = useCallback(async (title: string, body: string) => { - const success = await repository.sendCustomNotification(title, body); - const msg = success ? `Notification sent: ${title}` : 'Failed to send notification'; - log.i(TAG, msg); - Toast.show({ type: success ? 'info' : 'error', text1: msg }); - }, []); - - const clearAllNotifications = useCallback(() => { - repository.clearAllNotifications(); - log.i(TAG, 'All notifications cleared'); - Toast.show({ type: 'info', text1: 'All notifications cleared' }); - }, []); - - const setIamPaused = useCallback(async (paused: boolean) => { - dispatch({ type: 'SET_IAM_PAUSED', payload: paused }); - repository.setPaused(paused); - await preferences.setIamPaused(paused); - const msg = paused ? 'In-app messages paused' : 'In-app messages resumed'; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const sendIamTrigger = useCallback((iamType: string) => { - repository.addTrigger('iam_type', iamType); - dispatch({ - type: 'ADD_TRIGGER', - payload: { key: 'iam_type', value: iamType }, - }); - const msg = `Sent In-App Message: ${iamType}`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const addAlias = useCallback((label: string, id: string) => { - repository.addAlias(label, id); - dispatch({ type: 'ADD_ALIAS', payload: { label, id } }); - const msg = `Alias added: ${label}`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const addAliases = useCallback((pairs: Record) => { - repository.addAliases(pairs); - const newEntries = toPairs(pairs); - dispatch({ type: 'ADD_ALIASES', payload: newEntries }); - const msg = `${newEntries.length} alias(es) added`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const addEmail = useCallback((email: string) => { - repository.addEmail(email); - dispatch({ type: 'ADD_EMAIL', payload: email }); - const msg = `Email added: ${email}`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const removeEmail = useCallback((email: string) => { - repository.removeEmail(email); - dispatch({ type: 'REMOVE_EMAIL', payload: email }); - log.i(TAG, `Email removed: ${email}`); - Toast.show({ type: 'info', text1: `Email removed: ${email}` }); - }, []); - - const addSms = useCallback((sms: string) => { - repository.addSms(sms); - dispatch({ type: 'ADD_SMS', payload: sms }); - const msg = `SMS added: ${sms}`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const removeSms = useCallback((sms: string) => { - repository.removeSms(sms); - dispatch({ type: 'REMOVE_SMS', payload: sms }); - log.i(TAG, `SMS removed: ${sms}`); - Toast.show({ type: 'info', text1: `SMS removed: ${sms}` }); - }, []); - - const addTag = useCallback((key: string, value: string) => { - repository.addTag(key, value); - dispatch({ type: 'ADD_TAG', payload: { key, value } }); - const msg = `Tag added: ${key}`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const addTags = useCallback((pairs: Record) => { - repository.addTags(pairs); - const newEntries = toPairs(pairs); - dispatch({ type: 'ADD_TAGS', payload: newEntries }); - const msg = `${newEntries.length} tag(s) added`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const removeSelectedTags = useCallback((keys: string[]) => { - repository.removeTags(keys); - dispatch({ type: 'REMOVE_SELECTED_TAGS', payload: keys }); - const msg = `${keys.length} tag(s) removed`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const sendOutcome = useCallback((name: string) => { - repository.sendOutcome(name); - log.i(TAG, `Outcome sent: ${name}`); - Toast.show({ type: 'info', text1: `Outcome sent: ${name}` }); - }, []); - - const sendUniqueOutcome = useCallback((name: string) => { - repository.sendUniqueOutcome(name); - log.i(TAG, `Unique outcome sent: ${name}`); - Toast.show({ type: 'info', text1: `Unique outcome sent: ${name}` }); - }, []); - - const sendOutcomeWithValue = useCallback((name: string, value: number) => { - repository.sendOutcomeWithValue(name, value); - log.i(TAG, `Outcome sent: ${name} = ${value}`); - Toast.show({ type: 'info', text1: `Outcome sent: ${name}` }); - }, []); - - const addTrigger = useCallback((key: string, value: string) => { - repository.addTrigger(key, value); - dispatch({ type: 'ADD_TRIGGER', payload: { key, value } }); - log.i(TAG, `Trigger added: ${key}`); - Toast.show({ type: 'info', text1: `Trigger added: ${key}` }); - }, []); - - const addTriggers = useCallback((pairs: Record) => { - repository.addTriggers(pairs); - const newEntries = toPairs(pairs); - dispatch({ type: 'ADD_TRIGGERS', payload: newEntries }); - const msg = `${newEntries.length} trigger(s) added`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const removeSelectedTriggers = useCallback((keys: string[]) => { - repository.removeTriggers(keys); - dispatch({ type: 'REMOVE_SELECTED_TRIGGERS', payload: keys }); - const msg = `${keys.length} trigger(s) removed`; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const clearTriggers = useCallback(() => { - repository.clearTriggers(); - dispatch({ type: 'CLEAR_TRIGGERS' }); - log.i(TAG, 'All triggers cleared'); - Toast.show({ type: 'info', text1: 'All triggers cleared' }); - }, []); - - const trackEvent = useCallback((name: string, properties?: Record) => { - repository.trackEvent(name, properties); - log.i(TAG, `Event tracked: ${name}`); - Toast.show({ type: 'info', text1: `Event tracked: ${name}` }); - }, []); - - const setLocationShared = useCallback(async (shared: boolean) => { - dispatch({ type: 'SET_LOCATION_SHARED', payload: shared }); - repository.setLocationShared(shared); - await preferences.setLocationShared(shared); - const msg = shared ? 'Location sharing enabled' : 'Location sharing disabled'; - log.i(TAG, msg); - Toast.show({ type: 'info', text1: msg }); - }, []); - - const requestLocationPermission = useCallback(() => { - repository.requestLocationPermission(); - }, []); - - const startDefaultLiveActivity = useCallback( - (activityId: string, attributes: object, content: object) => { - repository.startDefaultLiveActivity(activityId, attributes, content); - log.i(TAG, `Started Live Activity: ${activityId}`); - Toast.show({ type: 'info', text1: `Started Live Activity: ${activityId}` }); - }, - [], - ); - - const updateLiveActivity = useCallback( - async (activityId: string, eventUpdates: Record) => { - const success = await repository.updateLiveActivity(activityId, 'update', eventUpdates); - const msg = success - ? `Updated Live Activity: ${activityId}` - : 'Failed to update Live Activity'; - log.i(TAG, msg); - Toast.show({ type: success ? 'info' : 'error', text1: msg }); - }, - [], - ); - - const endLiveActivity = useCallback(async (activityId: string) => { - const success = await repository.updateLiveActivity(activityId, 'end', { - message: 'Ended Live Activity', - }); - const msg = success ? `Ended Live Activity: ${activityId}` : 'Failed to end Live Activity'; - log.i(TAG, msg); - Toast.show({ type: success ? 'info' : 'error', text1: msg }); - }, []); - - const contextValue = useMemo( - () => ({ - state, - loginUser, - logoutUser, - setConsentRequired, - setConsentGiven, - promptPush, - setPushEnabled, - sendNotification, - sendCustomNotification, - clearAllNotifications, - setIamPaused, - sendIamTrigger, - addAlias, - addAliases, - addEmail, - removeEmail, - addSms, - removeSms, - addTag, - addTags, - removeSelectedTags, - sendOutcome, - sendUniqueOutcome, - sendOutcomeWithValue, - addTrigger, - addTriggers, - removeSelectedTriggers, - clearTriggers, - trackEvent, - setLocationShared, - requestLocationPermission, - startDefaultLiveActivity: startDefaultLiveActivity, - updateLiveActivity, - endLiveActivity, - }), - [ - state, - loginUser, - logoutUser, - setConsentRequired, - setConsentGiven, - promptPush, - setPushEnabled, - sendNotification, - sendCustomNotification, - clearAllNotifications, - setIamPaused, - sendIamTrigger, - addAlias, - addAliases, - addEmail, - removeEmail, - addSms, - removeSms, - addTag, - addTags, - removeSelectedTags, - sendOutcome, - sendUniqueOutcome, - sendOutcomeWithValue, - addTrigger, - addTriggers, - removeSelectedTriggers, - clearTriggers, - trackEvent, - setLocationShared, - requestLocationPermission, - startDefaultLiveActivity, - updateLiveActivity, - endLiveActivity, - ], - ); - - return {children}; -} - -export function useAppContext(): AppContextValue { - const ctx = useContext(AppContext); - if (!ctx) { - throw new Error('useAppContext must be used within AppContextProvider'); - } - return ctx; -} diff --git a/examples/demo/src/hooks/useOneSignal.ts b/examples/demo/src/hooks/useOneSignal.ts new file mode 100644 index 00000000..87d6d251 --- /dev/null +++ b/examples/demo/src/hooks/useOneSignal.ts @@ -0,0 +1,616 @@ +import { ONESIGNAL_APP_ID } from '@env'; +import { + createContext, + createElement, + useCallback, + useContext, + useEffect, + useRef, + useState, + type ReactNode, +} from 'react'; +import { + LogLevel, + OneSignal, + type InAppMessageClickEvent, + type InAppMessageDidDismissEvent, + type InAppMessageDidDisplayEvent, + type InAppMessageWillDismissEvent, + type InAppMessageWillDisplayEvent, + type NotificationClickEvent, + type NotificationWillDisplayEvent, + type UserChangedState, +} from 'react-native-onesignal'; + +import { NotificationType } from '../models/NotificationType'; +import OneSignalApiService from '../services/OneSignalApiService'; +import PreferencesService from '../services/PreferencesService'; + +const DEFAULT_APP_ID = '77e32082-ea27-42e3-a898-c72e141824ef'; + +function resolveAppId(): string { + return ONESIGNAL_APP_ID?.trim() || DEFAULT_APP_ID; +} + +const apiService = OneSignalApiService.getInstance(); +const preferences = PreferencesService.getInstance(); + +async function postNotification(type: NotificationType): Promise { + const subscriptionId = await OneSignal.User.pushSubscription.getIdAsync(); + if (!subscriptionId) return false; + return apiService.sendNotification(type, subscriptionId); +} + +async function postCustomNotification(title: string, body: string): Promise { + const subscriptionId = await OneSignal.User.pushSubscription.getIdAsync(); + if (!subscriptionId) return false; + return apiService.sendCustomNotification(title, body, subscriptionId); +} + +function toPairs(pairs: Record): [string, string][] { + return Object.entries(pairs).map(([key, value]) => [key, value]); +} + +export type UseOneSignalReturn = { + appId: string; + consentRequired: boolean; + privacyConsentGiven: boolean; + externalUserId: string | undefined; + pushSubscriptionId: string | undefined; + isPushEnabled: boolean; + hasNotificationPermission: boolean; + inAppMessagesPaused: boolean; + locationShared: boolean; + aliasesList: [string, string][]; + emailsList: string[]; + smsNumbersList: string[]; + tagsList: [string, string][]; + triggersList: [string, string][]; + isLoading: boolean; + isReady: boolean; + loginUser: (externalUserId: string) => Promise; + logoutUser: () => Promise; + setConsentRequired: (required: boolean) => Promise; + setConsentGiven: (granted: boolean) => Promise; + promptPush: () => void; + setPushEnabled: (enabled: boolean) => void; + sendNotification: (type: NotificationType) => Promise; + sendCustomNotification: (title: string, body: string) => Promise; + clearAllNotifications: () => void; + setIamPaused: (paused: boolean) => Promise; + sendIamTrigger: (iamType: string) => void; + addAlias: (label: string, id: string) => void; + addAliases: (pairs: Record) => void; + addEmail: (email: string) => void; + removeEmail: (email: string) => void; + addSms: (sms: string) => void; + removeSms: (sms: string) => void; + addTag: (key: string, value: string) => void; + addTags: (pairs: Record) => void; + removeSelectedTags: (keys: string[]) => void; + sendOutcome: (name: string) => void; + sendUniqueOutcome: (name: string) => void; + sendOutcomeWithValue: (name: string, value: number) => void; + addTrigger: (key: string, value: string) => void; + addTriggers: (pairs: Record) => void; + removeSelectedTriggers: (keys: string[]) => void; + clearTriggers: () => void; + trackEvent: (name: string, properties?: Record) => void; + setLocationShared: (shared: boolean) => Promise; + checkLocationShared: () => Promise; + requestLocationPermission: () => void; + startDefaultLiveActivity: (activityId: string, attributes: object, content: object) => void; + updateLiveActivity: (activityId: string, eventUpdates: Record) => Promise; + endLiveActivity: (activityId: string) => Promise; +}; + +function useOneSignalState(): UseOneSignalReturn { + const [appId, setAppId] = useState(resolveAppId); + const [consentRequired, setConsentRequiredState] = useState(false); + const [privacyConsentGiven, setPrivacyConsentGivenState] = useState(false); + const [externalUserId, setExternalUserId] = useState(undefined); + const [pushSubscriptionId, setPushSubscriptionId] = useState(undefined); + const [isPushEnabled, setIsPushEnabled] = useState(false); + const [hasNotificationPermission, setHasNotificationPermission] = useState(false); + const [inAppMessagesPaused, setInAppMessagesPaused] = useState(false); + const [locationShared, setLocationSharedState] = useState(false); + const [aliasesList, setAliasesList] = useState<[string, string][]>([]); + const [emailsList, setEmailsList] = useState([]); + const [smsNumbersList, setSmsNumbersList] = useState([]); + const [tagsList, setTagsList] = useState<[string, string][]>([]); + const [triggersList, setTriggersList] = useState<[string, string][]>([]); + const [isLoading, setIsLoading] = useState(false); + const [isReady, setIsReady] = useState(false); + + const requestSequenceRef = useRef(0); + + const fetchUserDataFromApi = useCallback(async () => { + const requestId = requestSequenceRef.current + 1; + requestSequenceRef.current = requestId; + setIsLoading(true); + + try { + const onesignalId = await OneSignal.User.getOnesignalId(); + if (!onesignalId) return; + + const userData = await apiService.fetchUser(onesignalId); + if (!userData) return; + + const externalId = await OneSignal.User.getExternalId(); + + if (requestSequenceRef.current !== requestId) return; + + setAliasesList(Object.entries(userData.aliases)); + setTagsList(Object.entries(userData.tags)); + setEmailsList(userData.emails); + setSmsNumbersList(userData.smsNumbers); + setExternalUserId(externalId ?? userData.externalId); + } finally { + if (requestSequenceRef.current === requestId) { + setIsLoading(false); + } + } + }, []); + + useEffect(() => { + const handleIamWillDisplay = (e: InAppMessageWillDisplayEvent) => { + console.log(`IAM willDisplay: ${e.message.messageId}`); + }; + + const handleIamDidDisplay = (e: InAppMessageDidDisplayEvent) => { + console.log(`IAM didDisplay: ${e.message.messageId}`); + }; + + const handleIamWillDismiss = (e: InAppMessageWillDismissEvent) => { + console.log(`IAM willDismiss: ${e.message.messageId}`); + }; + + const handleIamDidDismiss = (e: InAppMessageDidDismissEvent) => { + console.log(`IAM didDismiss: ${e.message.messageId}`); + }; + + const handleIamClick = (e: InAppMessageClickEvent) => { + console.log(`IAM click: ${e.result.actionId ?? 'unknown'}`); + }; + + const handleNotificationClick = (e: NotificationClickEvent) => { + console.log(`Notification click: ${e.notification.title ?? ''}`); + }; + + const handleForegroundWillDisplay = (e: NotificationWillDisplayEvent) => { + console.log(`Notification foregroundWillDisplay: ${e.getNotification().title ?? ''}`); + e.getNotification().display(); + }; + + const pushSubHandler = async () => { + const [id, optedIn] = await Promise.all([ + OneSignal.User.pushSubscription.getIdAsync(), + OneSignal.User.pushSubscription.getOptedInAsync(), + ]); + setPushSubscriptionId(id ?? undefined); + setIsPushEnabled(optedIn); + }; + + const permissionHandler = async () => { + setHasNotificationPermission(await OneSignal.Notifications.getPermissionAsync()); + }; + + const userChangeHandler = (event: UserChangedState) => { + const nextOnesignalId = event.current.onesignalId ?? null; + console.log( + `User changed: onesignalId=${nextOnesignalId ?? 'null'}, externalId=${event.current.externalId ?? 'null'}`, + ); + + if (nextOnesignalId === null) { + return; + } + + fetchUserDataFromApi(); + }; + + const load = async () => { + const nextAppId = resolveAppId(); + const [nextConsentRequired, nextPrivacyConsentGiven, nextIamPaused, nextLocationShared] = + await Promise.all([ + preferences.getConsentRequired(), + preferences.getPrivacyConsent(), + preferences.getIamPaused(), + preferences.getLocationShared(), + ]); + const storedExternalUserId = (await preferences.getExternalUserId()) ?? undefined; + + apiService.setAppId(nextAppId); + + OneSignal.Debug.setLogLevel(LogLevel.Verbose); + OneSignal.setConsentRequired(nextConsentRequired); + OneSignal.setConsentGiven(nextPrivacyConsentGiven); + OneSignal.initialize(nextAppId); + + OneSignal.LiveActivities.setupDefault({ + enablePushToStart: true, + enablePushToUpdate: true, + }); + + OneSignal.InAppMessages.setPaused(nextIamPaused); + OneSignal.Location.setShared(nextLocationShared); + + if (storedExternalUserId) { + OneSignal.login(storedExternalUserId); + } + + OneSignal.InAppMessages.addEventListener('willDisplay', handleIamWillDisplay); + OneSignal.InAppMessages.addEventListener('didDisplay', handleIamDidDisplay); + OneSignal.InAppMessages.addEventListener('willDismiss', handleIamWillDismiss); + OneSignal.InAppMessages.addEventListener('didDismiss', handleIamDidDismiss); + OneSignal.InAppMessages.addEventListener('click', handleIamClick); + OneSignal.Notifications.addEventListener('click', handleNotificationClick); + OneSignal.Notifications.addEventListener('permissionChange', permissionHandler); + OneSignal.Notifications.addEventListener( + 'foregroundWillDisplay', + handleForegroundWillDisplay, + ); + + OneSignal.User.pushSubscription.addEventListener('change', pushSubHandler); + OneSignal.User.addEventListener('change', userChangeHandler); + + console.log(`OneSignal initialized with app ID: ${nextAppId}`); + + const externalId = await OneSignal.User.getExternalId(); + const [pushId, pushOptedIn, hasPerm] = await Promise.all([ + OneSignal.User.pushSubscription.getIdAsync(), + OneSignal.User.pushSubscription.getOptedInAsync(), + OneSignal.Notifications.getPermissionAsync(), + ]); + + setAppId(nextAppId); + setConsentRequiredState(nextConsentRequired); + setPrivacyConsentGivenState(nextPrivacyConsentGiven); + setInAppMessagesPaused(nextIamPaused); + setLocationSharedState(nextLocationShared); + setExternalUserId(externalId ?? storedExternalUserId); + setPushSubscriptionId(pushId ?? undefined); + setIsPushEnabled(pushOptedIn); + setHasNotificationPermission(hasPerm); + setIsReady(true); + + const initialOnesignalId = await OneSignal.User.getOnesignalId(); + if (initialOnesignalId) { + await fetchUserDataFromApi(); + } + }; + + void load().catch((err) => { + console.error(`Initial load error: ${String(err)}`); + setIsLoading(false); + }); + + return () => { + OneSignal.InAppMessages.removeEventListener('willDisplay', handleIamWillDisplay); + OneSignal.InAppMessages.removeEventListener('didDisplay', handleIamDidDisplay); + OneSignal.InAppMessages.removeEventListener('willDismiss', handleIamWillDismiss); + OneSignal.InAppMessages.removeEventListener('didDismiss', handleIamDidDismiss); + OneSignal.InAppMessages.removeEventListener('click', handleIamClick); + OneSignal.Notifications.removeEventListener('click', handleNotificationClick); + OneSignal.Notifications.removeEventListener('permissionChange', permissionHandler); + OneSignal.Notifications.removeEventListener( + 'foregroundWillDisplay', + handleForegroundWillDisplay, + ); + OneSignal.User.pushSubscription.removeEventListener('change', pushSubHandler); + OneSignal.User.removeEventListener('change', userChangeHandler); + }; + }, [fetchUserDataFromApi]); + + const loginUser = async (nextExternalUserId: string) => { + setAliasesList([]); + setEmailsList([]); + setSmsNumbersList([]); + setTagsList([]); + setTriggersList([]); + setIsLoading(true); + + try { + OneSignal.login(nextExternalUserId); + await preferences.setExternalUserId(nextExternalUserId); + setExternalUserId(nextExternalUserId); + console.log(`Logged in as: ${nextExternalUserId}`); + // The user 'change' listener runs fetchUserDataFromApi once the new + // onesignalId is assigned; that call clears isLoading in its finally. + } catch (err) { + console.error(`Login error: ${String(err)}`); + setIsLoading(false); + } + }; + + const logoutUser = async () => { + OneSignal.logout(); + await preferences.setExternalUserId(null); + setExternalUserId(undefined); + setAliasesList([]); + setEmailsList([]); + setSmsNumbersList([]); + setTagsList([]); + setTriggersList([]); + console.log('Logged out'); + }; + + const setConsentRequired = async (required: boolean) => { + setConsentRequiredState(required); + OneSignal.setConsentRequired(required); + await preferences.setConsentRequired(required); + }; + + const setConsentGiven = async (granted: boolean) => { + setPrivacyConsentGivenState(granted); + OneSignal.setConsentGiven(granted); + await preferences.setPrivacyConsent(granted); + }; + + const promptPush = () => { + OneSignal.Notifications.requestPermission(true); + }; + + const setPushEnabled = (enabled: boolean) => { + if (enabled) { + OneSignal.User.pushSubscription.optIn(); + } else { + OneSignal.User.pushSubscription.optOut(); + } + setIsPushEnabled(enabled); + console.log(enabled ? 'Push enabled' : 'Push disabled'); + }; + + const sendNotification = async (type: NotificationType) => { + const success = await postNotification(type); + console.log(success ? `Notification sent: ${type}` : 'Failed to send notification'); + }; + + const sendCustomNotification = async (title: string, body: string) => { + const success = await postCustomNotification(title, body); + console.log(success ? `Notification sent: ${title}` : 'Failed to send notification'); + }; + + const clearAllNotifications = () => { + OneSignal.Notifications.clearAll(); + console.log('All notifications cleared'); + }; + + const setIamPaused = async (paused: boolean) => { + setInAppMessagesPaused(paused); + OneSignal.InAppMessages.setPaused(paused); + await preferences.setIamPaused(paused); + console.log(paused ? 'In-app messages paused' : 'In-app messages resumed'); + }; + + const sendIamTrigger = (iamType: string) => { + OneSignal.InAppMessages.addTrigger('iam_type', iamType); + setTriggersList((prev) => { + const filtered = prev.filter(([key]) => key !== 'iam_type'); + return [...filtered, ['iam_type', iamType] as [string, string]]; + }); + console.log(`Sent In-App Message: ${iamType}`); + }; + + const addAlias = (label: string, id: string) => { + OneSignal.User.addAlias(label, id); + setAliasesList((prev) => [...prev, [label, id]]); + console.log(`Alias added: ${label}`); + }; + + const addAliases = (pairs: Record) => { + OneSignal.User.addAliases(pairs); + const newEntries = toPairs(pairs); + setAliasesList((prev) => [...prev, ...newEntries]); + console.log(`${newEntries.length} alias(es) added`); + }; + + const addEmail = (email: string) => { + OneSignal.User.addEmail(email); + setEmailsList((prev) => [...prev.filter((value) => value !== email), email]); + console.log(`Email added: ${email}`); + }; + + const removeEmail = (email: string) => { + OneSignal.User.removeEmail(email); + setEmailsList((prev) => prev.filter((value) => value !== email)); + console.log(`Email removed: ${email}`); + }; + + const addSms = (sms: string) => { + OneSignal.User.addSms(sms); + setSmsNumbersList((prev) => [...prev.filter((value) => value !== sms), sms]); + console.log(`SMS added: ${sms}`); + }; + + const removeSms = (sms: string) => { + OneSignal.User.removeSms(sms); + setSmsNumbersList((prev) => prev.filter((value) => value !== sms)); + console.log(`SMS removed: ${sms}`); + }; + + const addTag = (key: string, value: string) => { + OneSignal.User.addTag(key, value); + setTagsList((prev) => { + const filtered = prev.filter(([k]) => k !== key); + return [...filtered, [key, value]]; + }); + console.log(`Tag added: ${key}`); + }; + + const addTags = (pairs: Record) => { + OneSignal.User.addTags(pairs); + const newEntries = toPairs(pairs); + setTagsList((prev) => { + const keys = new Set(newEntries.map(([k]) => k)); + return [...prev.filter(([k]) => !keys.has(k)), ...newEntries]; + }); + console.log(`${newEntries.length} tag(s) added`); + }; + + const removeSelectedTags = (keys: string[]) => { + OneSignal.User.removeTags(keys); + const keySet = new Set(keys); + setTagsList((prev) => prev.filter(([k]) => !keySet.has(k))); + console.log(`${keys.length} tag(s) removed`); + }; + + const sendOutcome = (name: string) => { + OneSignal.Session.addOutcome(name); + console.log(`Outcome sent: ${name}`); + }; + + const sendUniqueOutcome = (name: string) => { + OneSignal.Session.addUniqueOutcome(name); + console.log(`Unique outcome sent: ${name}`); + }; + + const sendOutcomeWithValue = (name: string, value: number) => { + OneSignal.Session.addOutcomeWithValue(name, value); + console.log(`Outcome sent: ${name} = ${value}`); + }; + + const addTrigger = (key: string, value: string) => { + OneSignal.InAppMessages.addTrigger(key, value); + setTriggersList((prev) => { + const filtered = prev.filter(([k]) => k !== key); + return [...filtered, [key, value]]; + }); + console.log(`Trigger added: ${key}`); + }; + + const addTriggers = (pairs: Record) => { + OneSignal.InAppMessages.addTriggers(pairs); + const newEntries = toPairs(pairs); + setTriggersList((prev) => { + const keys = new Set(newEntries.map(([k]) => k)); + return [...prev.filter(([k]) => !keys.has(k)), ...newEntries]; + }); + console.log(`${newEntries.length} trigger(s) added`); + }; + + const removeSelectedTriggers = (keys: string[]) => { + OneSignal.InAppMessages.removeTriggers(keys); + const keySet = new Set(keys); + setTriggersList((prev) => prev.filter(([k]) => !keySet.has(k))); + console.log(`${keys.length} trigger(s) removed`); + }; + + const clearTriggers = () => { + OneSignal.InAppMessages.clearTriggers(); + setTriggersList([]); + console.log('All triggers cleared'); + }; + + const trackEvent = (name: string, properties?: Record) => { + OneSignal.User.trackEvent(name, properties); + console.log(`Event tracked: ${name}`); + }; + + const setLocationShared = async (shared: boolean) => { + setLocationSharedState(shared); + OneSignal.Location.setShared(shared); + await preferences.setLocationShared(shared); + console.log(shared ? 'Location sharing enabled' : 'Location sharing disabled'); + }; + + const checkLocationShared = async () => { + const shared = await OneSignal.Location.isShared(); + console.log(`Location shared: ${shared}`); + return shared; + }; + + const requestLocationPermission = () => { + OneSignal.Location.requestPermission(); + }; + + const startDefaultLiveActivity = (activityId: string, attributes: object, content: object) => { + OneSignal.LiveActivities.startDefault(activityId, attributes, content); + console.log(`Started Live Activity: ${activityId}`); + }; + + const updateLiveActivity = async (activityId: string, eventUpdates: Record) => { + const success = await apiService.updateLiveActivity(activityId, 'update', eventUpdates); + console.log( + success ? `Updated Live Activity: ${activityId}` : 'Failed to update Live Activity', + ); + }; + + const endLiveActivity = async (activityId: string) => { + const success = await apiService.updateLiveActivity(activityId, 'end', { + message: 'Ended Live Activity', + }); + console.log(success ? `Ended Live Activity: ${activityId}` : 'Failed to end Live Activity'); + }; + + return { + appId, + consentRequired, + privacyConsentGiven, + externalUserId, + pushSubscriptionId, + isPushEnabled, + hasNotificationPermission, + inAppMessagesPaused, + locationShared, + aliasesList, + emailsList, + smsNumbersList, + tagsList, + triggersList, + isLoading, + isReady, + loginUser, + logoutUser, + setConsentRequired, + setConsentGiven, + promptPush, + setPushEnabled, + sendNotification, + sendCustomNotification, + clearAllNotifications, + setIamPaused, + sendIamTrigger, + addAlias, + addAliases, + addEmail, + removeEmail, + addSms, + removeSms, + addTag, + addTags, + removeSelectedTags, + sendOutcome, + sendUniqueOutcome, + sendOutcomeWithValue, + addTrigger, + addTriggers, + removeSelectedTriggers, + clearTriggers, + trackEvent, + setLocationShared, + checkLocationShared, + requestLocationPermission, + startDefaultLiveActivity, + updateLiveActivity, + endLiveActivity, + }; +} + +const OneSignalContext = createContext(null); + +interface ProviderProps { + children: ReactNode; +} + +export function OneSignalProvider({ children }: ProviderProps) { + const value = useOneSignalState(); + return createElement(OneSignalContext.Provider, { value }, children); +} + +export function useOneSignal(): UseOneSignalReturn { + const ctx = useContext(OneSignalContext); + if (!ctx) { + throw new Error('useOneSignal must be used within '); + } + return ctx; +} diff --git a/examples/demo/src/repositories/OneSignalRepository.ts b/examples/demo/src/repositories/OneSignalRepository.ts deleted file mode 100644 index fbeedd8c..00000000 --- a/examples/demo/src/repositories/OneSignalRepository.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { OneSignal } from 'react-native-onesignal'; - -import { NotificationType } from '../models/NotificationType'; -import { UserData } from '../models/UserData'; -import OneSignalApiService from '../services/OneSignalApiService'; - -class OneSignalRepository { - private apiService: OneSignalApiService; - - constructor(apiService: OneSignalApiService) { - this.apiService = apiService; - } - - // User - loginUser(externalUserId: string): void { - OneSignal.login(externalUserId); - } - - logoutUser(): void { - OneSignal.logout(); - } - - // Aliases - addAlias(label: string, id: string): void { - OneSignal.User.addAlias(label, id); - } - - addAliases(aliases: Record): void { - OneSignal.User.addAliases(aliases); - } - - // Email - addEmail(email: string): void { - OneSignal.User.addEmail(email); - } - - removeEmail(email: string): void { - OneSignal.User.removeEmail(email); - } - - // SMS - addSms(smsNumber: string): void { - OneSignal.User.addSms(smsNumber); - } - - removeSms(smsNumber: string): void { - OneSignal.User.removeSms(smsNumber); - } - - // Tags - addTag(key: string, value: string): void { - OneSignal.User.addTag(key, value); - } - - addTags(tags: Record): void { - OneSignal.User.addTags(tags); - } - - removeTags(keys: string[]): void { - OneSignal.User.removeTags(keys); - } - - // Triggers - addTrigger(key: string, value: string): void { - OneSignal.InAppMessages.addTrigger(key, value); - } - - addTriggers(triggers: Record): void { - OneSignal.InAppMessages.addTriggers(triggers); - } - - removeTriggers(keys: string[]): void { - OneSignal.InAppMessages.removeTriggers(keys); - } - - clearTriggers(): void { - OneSignal.InAppMessages.clearTriggers(); - } - - // Outcomes - sendOutcome(name: string): void { - OneSignal.Session.addOutcome(name); - } - - sendUniqueOutcome(name: string): void { - OneSignal.Session.addUniqueOutcome(name); - } - - sendOutcomeWithValue(name: string, value: number): void { - OneSignal.Session.addOutcomeWithValue(name, value); - } - - // Track Event - trackEvent(name: string, properties?: Record): void { - OneSignal.User.trackEvent(name, properties); - } - - // Push Subscription - async getPushSubscriptionIdAsync(): Promise { - const id = await OneSignal.User.pushSubscription.getIdAsync(); - return id ?? undefined; - } - - async isPushOptedInAsync(): Promise { - return OneSignal.User.pushSubscription.getOptedInAsync(); - } - - optInPush(): void { - OneSignal.User.pushSubscription.optIn(); - } - - optOutPush(): void { - OneSignal.User.pushSubscription.optOut(); - } - - // Notifications - async hasPermission(): Promise { - return OneSignal.Notifications.getPermissionAsync(); - } - - async requestPermission(fallbackToSettings: boolean): Promise { - return OneSignal.Notifications.requestPermission(fallbackToSettings); - } - - clearAllNotifications(): void { - OneSignal.Notifications.clearAll(); - } - - // In-App Messages - setPaused(paused: boolean): void { - OneSignal.InAppMessages.setPaused(paused); - } - - // Location - setLocationShared(shared: boolean): void { - OneSignal.Location.setShared(shared); - } - - requestLocationPermission(): void { - OneSignal.Location.requestPermission(); - } - - // Privacy Consent - setConsentRequired(required: boolean): void { - OneSignal.setConsentRequired(required); - } - - setConsentGiven(granted: boolean): void { - OneSignal.setConsentGiven(granted); - } - - // User IDs - async getExternalId(): Promise { - const id = await OneSignal.User.getExternalId(); - return id ?? undefined; - } - - async getOnesignalId(): Promise { - const id = await OneSignal.User.getOnesignalId(); - return id ?? undefined; - } - - // Notification sending (via REST API) - async sendNotification(type: NotificationType): Promise { - const subscriptionId = await this.getPushSubscriptionIdAsync(); - if (!subscriptionId) { - return false; - } - return this.apiService.sendNotification(type, subscriptionId); - } - - async sendCustomNotification(title: string, body: string): Promise { - const subscriptionId = await this.getPushSubscriptionIdAsync(); - if (!subscriptionId) { - return false; - } - return this.apiService.sendCustomNotification(title, body, subscriptionId); - } - - async fetchUser(onesignalId: string): Promise { - return this.apiService.fetchUser(onesignalId); - } - - // Live Activities - startDefaultLiveActivity(activityId: string, attributes: object, content: object): void { - OneSignal.LiveActivities.startDefault(activityId, attributes, content); - } - - async updateLiveActivity( - activityId: string, - event: 'update' | 'end', - eventUpdates?: Record, - ): Promise { - return this.apiService.updateLiveActivity(activityId, event, eventUpdates); - } -} - -export default OneSignalRepository; diff --git a/examples/demo/src/screens/HomeScreen.tsx b/examples/demo/src/screens/HomeScreen.tsx index 07d453d1..9006d71a 100644 --- a/examples/demo/src/screens/HomeScreen.tsx +++ b/examples/demo/src/screens/HomeScreen.tsx @@ -3,8 +3,6 @@ import React, { useEffect, useState } from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; import ActionButton from '../components/ActionButton'; -import LoadingOverlay from '../components/LoadingOverlay'; -import LogView from '../components/LogView'; import TooltipModal from '../components/modals/TooltipModal'; import AliasesSection from '../components/sections/AliasesSection'; import AppSection from '../components/sections/AppSection'; @@ -21,24 +19,22 @@ import TagsSection from '../components/sections/TagsSection'; import TrackEventSection from '../components/sections/TrackEventSection'; import TriggersSection from '../components/sections/TriggersSection'; import UserSection from '../components/sections/UserSection'; -import { useAppContext } from '../context/AppContext'; +import { useOneSignal } from '../hooks/useOneSignal'; import { InAppMessageType } from '../models/InAppMessageType'; +import OneSignalApiService from '../services/OneSignalApiService'; import TooltipHelper, { TooltipData } from '../services/TooltipHelper'; import { AppColors } from '../theme'; export default function HomeScreen() { const navigation = useNavigation(); - const app = useAppContext(); - const { state } = app; + const os = useOneSignal(); const [tooltipVisible, setTooltipVisible] = useState(false); const [activeTooltip, setActiveTooltip] = useState(null); - // Auto-request push permission on load useEffect(() => { - void app.promptPush(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (os.isReady) os.promptPush(); + }, [os.isReady, os.promptPush]); const showTooltipModal = (key: string) => { const tooltip = TooltipHelper.getInstance().getTooltip(key); @@ -50,119 +46,123 @@ export default function HomeScreen() { return ( - {/* Sticky Log View */} - - showTooltipModal('push')} /> showTooltipModal('sendPushNotification')} /> showTooltipModal('inAppMessaging')} /> app.sendIamTrigger(type)} + onSendIam={(type: InAppMessageType) => os.sendIamTrigger(type)} onInfoTap={() => showTooltipModal('sendInAppMessage')} /> showTooltipModal('aliases')} /> showTooltipModal('emails')} /> showTooltipModal('sms')} /> showTooltipModal('tags')} /> showTooltipModal('outcomes')} /> showTooltipModal('triggers')} /> showTooltipModal('trackEvent')} + onTrackEvent={os.trackEvent} + onInfoTap={() => showTooltipModal('customEvents')} /> showTooltipModal('location')} /> {Platform.OS === 'ios' && ( showTooltipModal('liveActivities')} /> )} @@ -179,8 +179,6 @@ export default function HomeScreen() { - - void; - -class LogManager { - private static _instance: LogManager; - private entries: LogEntry[] = []; - private listeners: Set = new Set(); - - static getInstance(): LogManager { - if (!LogManager._instance) { - LogManager._instance = new LogManager(); - } - return LogManager._instance; - } - - private log(level: LogLevel, tag: string, message: string): void { - const now = new Date(); - const timestamp = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; - const entry: LogEntry = { timestamp, level, tag, message }; - this.entries.push(entry); - - switch (level) { - case 'W': - console.warn(`[${tag}] ${message}`); - break; - case 'E': - console.error(`[${tag}] ${message}`); - break; - default: - console.log(`[${level}][${tag}] ${message}`); - } - - this.notify(entry); - } - - d(tag: string, message: string): void { - this.log('D', tag, message); - } - - i(tag: string, message: string): void { - this.log('I', tag, message); - } - - w(tag: string, message: string): void { - this.log('W', tag, message); - } - - e(tag: string, message: string): void { - this.log('E', tag, message); - } - - clear(): void { - this.entries = []; - this.notify(null); - } - - getEntries(): LogEntry[] { - return this.entries; - } - - subscribe(listener: Listener): () => void { - this.listeners.add(listener); - return () => this.listeners.delete(listener); - } - - private notify(entry: LogEntry | null): void { - for (const listener of this.listeners) { - listener(entry); - } - } -} - -export default LogManager; diff --git a/examples/demo/src/services/OneSignalApiService.ts b/examples/demo/src/services/OneSignalApiService.ts index 0fb99bd5..98140c5c 100644 --- a/examples/demo/src/services/OneSignalApiService.ts +++ b/examples/demo/src/services/OneSignalApiService.ts @@ -2,9 +2,6 @@ import { ONESIGNAL_API_KEY } from '@env'; import { NotificationType } from '../models/NotificationType'; import { UserData, userDataFromJson } from '../models/UserData'; -import LogManager from './LogManager'; - -const TAG = 'OneSignalApiService'; class OneSignalApiService { private static _instance: OneSignalApiService; @@ -25,6 +22,10 @@ class OneSignalApiService { return this._appId; } + hasApiKey(): boolean { + return !!ONESIGNAL_API_KEY?.trim(); + } + async sendNotification(type: NotificationType, subscriptionId: string): Promise { let headings: Record; let contents: Record; @@ -91,13 +92,13 @@ class OneSignalApiService { if (!response.ok) { const text = await response.text(); - LogManager.getInstance().e(TAG, `Send notification failed: ${text}`); + console.error(`Send notification failed: ${text}`); return false; } return true; } catch (err) { - LogManager.getInstance().e(TAG, `Send notification error: ${String(err)}`); + console.error(`Send notification error: ${String(err)}`); return false; } } @@ -131,13 +132,13 @@ class OneSignalApiService { if (!response.ok) { const text = await response.text(); - LogManager.getInstance().e(TAG, `${event} live activity failed: ${text}`); + console.error(`${event} live activity failed: ${text}`); return false; } return true; } catch (err) { - LogManager.getInstance().e(TAG, `${event} live activity error: ${String(err)}`); + console.error(`${event} live activity error: ${String(err)}`); return false; } } @@ -147,13 +148,13 @@ class OneSignalApiService { const url = `https://api.onesignal.com/apps/${this._appId}/users/by/onesignal_id/${onesignalId}`; const response = await fetch(url); if (!response.ok) { - LogManager.getInstance().w(TAG, `fetchUser failed: ${response.status}`); + console.warn(`fetchUser failed: ${response.status}`); return null; } const json = (await response.json()) as Record; return userDataFromJson(json); } catch (err) { - LogManager.getInstance().e(TAG, `fetchUser error: ${String(err)}`); + console.error(`fetchUser error: ${String(err)}`); return null; } } diff --git a/examples/demo/src/services/PreferencesService.ts b/examples/demo/src/services/PreferencesService.ts index 55be39fd..2ac64eeb 100644 --- a/examples/demo/src/services/PreferencesService.ts +++ b/examples/demo/src/services/PreferencesService.ts @@ -1,7 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; const KEYS = { - APP_ID: 'onesignal_app_id', CONSENT_REQUIRED: 'onesignal_consent_required', PRIVACY_CONSENT: 'onesignal_privacy_consent', EXTERNAL_USER_ID: 'onesignal_external_user_id', @@ -19,15 +18,6 @@ class PreferencesService { return PreferencesService._instance; } - async getAppId(): Promise { - const value = await AsyncStorage.getItem(KEYS.APP_ID); - return value ?? '77e32082-ea27-42e3-a898-c72e141824ef'; - } - - async setAppId(appId: string): Promise { - await AsyncStorage.setItem(KEYS.APP_ID, appId); - } - async getConsentRequired(): Promise { const value = await AsyncStorage.getItem(KEYS.CONSENT_REQUIRED); return value === 'true'; diff --git a/examples/demo/src/theme.ts b/examples/demo/src/theme.ts index 5e2977c6..e8e20416 100644 --- a/examples/demo/src/theme.ts +++ b/examples/demo/src/theme.ts @@ -12,12 +12,6 @@ export const AppColors = { osDivider: '#E8EAED', osWarningBackground: '#FFF8E1', osBackdrop: 'rgba(0,0,0,0.54)', - osLogBackground: '#1A1B1E', - osLogDebug: '#82AAFF', - osLogInfo: '#C3E88D', - osLogWarn: '#FFCB6B', - osLogError: '#FF5370', - osLogTimestamp: '#676E7B', white: '#FFFFFF', } as const; @@ -31,7 +25,6 @@ export const AppTextStyles = StyleSheet.create({ bodyLarge: { fontSize: 16, fontWeight: '400' }, bodyMedium: { fontSize: 14, fontWeight: '400' }, bodySmall: { fontSize: 12, fontWeight: '400' }, - labelSmall: { fontSize: 11, fontWeight: '500' }, }); export const AppTheme = StyleSheet.create({ diff --git a/examples/demo/src/utils/showSnackbar.ts b/examples/demo/src/utils/showSnackbar.ts new file mode 100644 index 00000000..7816cc39 --- /dev/null +++ b/examples/demo/src/utils/showSnackbar.ts @@ -0,0 +1,6 @@ +import Toast from 'react-native-toast-message'; + +export function showSnackbar(message: string): void { + Toast.hide(); + Toast.show({ type: 'info', text1: message }); +} diff --git a/examples/demo/types/env.d.ts b/examples/demo/types/env.d.ts index 0ed07b4f..b334533f 100644 --- a/examples/demo/types/env.d.ts +++ b/examples/demo/types/env.d.ts @@ -1,3 +1,5 @@ declare module '@env' { + export const ONESIGNAL_APP_ID: string; export const ONESIGNAL_API_KEY: string; + export const E2E_MODE: string; } diff --git a/examples/setup.sh b/examples/setup.sh index 33157c0c..5c56133a 100755 --- a/examples/setup.sh +++ b/examples/setup.sh @@ -1,21 +1,71 @@ set -euo pipefail +# Invoked from a demo dir (e.g. examples/demo/) via `bun run setup`. +# ORIGINAL_DIR captures that dir so we can return to it after building +# the SDK; SDK_ROOT is two levels up (the SDK package itself). ORIGINAL_DIR=$(pwd) +SDK_ROOT="$(cd ../../ && pwd)" +STAMP_FILE="$SDK_ROOT/.rn-sdk-source.stamp" +TGZ_FILE="$SDK_ROOT/react-native-onesignal.tgz" +INSTALLED_DIR="$ORIGINAL_DIR/node_modules/react-native-onesignal" -# Build root package -cd ../../ +# Content hash of every input that can affect the published tarball. +# We deliberately hash file contents (shasum each file, then shasum the +# combined list) instead of using `find -newer`, because mtimes get +# bumped by routine git operations (checkout, branch switch, rebase) +# even when the source is identical — that caused needless rebuilds. +src_hash=$(find "$SDK_ROOT/src" "$SDK_ROOT/ios" "$SDK_ROOT/android" \ + "$SDK_ROOT/package.json" "$SDK_ROOT/tsconfig.json" \ + "$SDK_ROOT"/*.podspec \ + -type f 2>/dev/null \ + | sort \ + | xargs shasum 2>/dev/null \ + | shasum \ + | awk '{print $1}') + +# Skip the whole rebuild when: +# - the demo already has the SDK installed, +# - the cached tarball is still on disk, and +# - the source hash matches the last successful build. +# FORCE_SETUP=1 bypasses the cache when something feels off. +if [ "${FORCE_SETUP:-0}" != "1" ] \ + && [ -d "$INSTALLED_DIR" ] \ + && [ -f "$STAMP_FILE" ] \ + && [ -f "$TGZ_FILE" ] \ + && [ "$(cat "$STAMP_FILE")" = "$src_hash" ]; then + echo "SDK source unchanged, skipping rebuild. Set FORCE_SETUP=1 to override." + exit 0 +fi + +cd "$SDK_ROOT" bun run build +# `bun pm pack` honors package.json's "files" field (so the tarball matches +# what would actually be published). The version suffix in the filename +# is unstable, so we normalize to react-native-onesignal.tgz for a +# deterministic path that package.json + the extract step can reference. rm -f react-native-onesignal*.tgz bun pm pack mv react-native-onesignal-*.tgz react-native-onesignal.tgz -# Use fresh install of the package cd "$ORIGINAL_DIR" -bun pm cache rm - -bun remove react-native-onesignal +# Always go through bun add so bun.lock's integrity hash for the tarball +# stays in sync with the freshly-built tarball on disk. A previous version +# of this script had a "hot path" that just untarred over node_modules +# directly, which was faster but left a stale sha512 in bun.lock — any +# subsequent `bun install` that re-resolved this entry (e.g. when the +# lockfile was touched by another dep) would fail with IntegrityCheckFailed. +# +# `bun remove` first because bun verifies the existing integrity hash +# before replacing the entry; without removing, a stale hash from a prior +# build causes `bun add` itself to fail. The relative `file:../../...` +# path is intentional — an absolute path would leak this machine's +# layout into the lockfile. +echo "Registering tarball with bun (refreshes bun.lock integrity hash)..." +bun remove react-native-onesignal 2>/dev/null || true bun add file:../../react-native-onesignal.tgz -cd ios && pod update OneSignalXCFramework --no-repo-update && cd .. +# Record the hash only after a successful build/install so that an +# interrupted run forces a full retry next time. +echo "$src_hash" > "$STAMP_FILE"