diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7643c98 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +# Cancel in-progress runs when a new commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run type check + run: pnpm run typecheck + + - name: Run tests with coverage + run: pnpm run test:coverage + + - name: Upload coverage reports + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + build: + runs-on: ubuntu-latest + needs: test + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm run build + + - name: Verify build output exists + run: | + test -d lib/commonjs || (echo "lib/commonjs not found" && exit 1) + test -d lib/module || (echo "lib/module not found" && exit 1) + test -d lib/typescript || (echo "lib/typescript not found" && exit 1) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..808d63d --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +node_modules + +# Build output (top-level lib/ directory only, not src/lib/) +/lib/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment +.env +.env.local +.env.*.local diff --git a/README.md b/README.md index b56c29d..ec64059 100644 --- a/README.md +++ b/README.md @@ -1 +1,302 @@ -# sdk-react-native +# @formo/react-native-analytics + +Formo Analytics SDK for React Native - Track wallet events and user analytics in mobile dApps. + +## Installation + +```bash +npm install @formo/react-native-analytics @react-native-async-storage/async-storage + +# or with yarn +yarn add @formo/react-native-analytics @react-native-async-storage/async-storage + +# or with pnpm +pnpm add @formo/react-native-analytics @react-native-async-storage/async-storage +``` + +### iOS Setup + +```bash +cd ios && pod install +``` + +## Quick Start + +### 1. Wrap your app with the provider + +```tsx +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { FormoAnalyticsProvider } from '@formo/react-native-analytics'; + +function App() { + return ( + + + + ); +} +``` + +### 2. Use the hook in your components + +```tsx +import { useFormo } from '@formo/react-native-analytics'; +import { useEffect } from 'react'; + +function HomeScreen() { + const formo = useFormo(); + + useEffect(() => { + // Track screen view + formo.screen('Home'); + }, []); + + const handleSignUp = () => { + // Track custom event + formo.track('Sign Up Button Pressed', { + screen: 'Home', + }); + }; + + return ; +} +``` + +## Wagmi Integration + +For dApps using Wagmi for wallet connections, you can enable automatic wallet event tracking: + +```tsx +import { QueryClient } from '@tanstack/react-query'; +import { createConfig } from 'wagmi'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { FormoAnalyticsProvider } from '@formo/react-native-analytics'; + +const queryClient = new QueryClient(); +const wagmiConfig = createConfig({ + // your wagmi config +}); + +function App() { + return ( + + + + ); +} +``` + +This automatically tracks: +- Wallet connections and disconnections +- Chain changes +- Signature requests (with QueryClient) +- Transaction events (with QueryClient) + +## API Reference + +### FormoAnalyticsProvider Props + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `writeKey` | `string` | Yes | Your Formo write key | +| `asyncStorage` | `AsyncStorageInterface` | Yes | AsyncStorage instance | +| `options` | `Options` | No | Configuration options | +| `disabled` | `boolean` | No | Disable analytics | + +### Options + +```typescript +interface Options { + // Wagmi integration + wagmi?: { + config: any; // Wagmi config from createConfig() + queryClient?: any; // TanStack QueryClient for mutation tracking + }; + + // App information + app?: { + name?: string; + version?: string; + build?: string; + bundleId?: string; + }; + + // Event batching + flushAt?: number; // Batch size (default: 20) + flushInterval?: number; // Flush interval in ms (default: 30000) + retryCount?: number; // Retry count (default: 3) + maxQueueSize?: number; // Max queue size in bytes (default: 500KB) + + // Autocapture control + autocapture?: boolean | { + connect?: boolean; + disconnect?: boolean; + signature?: boolean; + transaction?: boolean; + chain?: boolean; + }; + + // Tracking control + tracking?: boolean | { + excludeChains?: number[]; + }; + + // Logging + logger?: { + enabled?: boolean; + levels?: ('debug' | 'info' | 'warn' | 'error' | 'log')[]; + }; + + // Custom API host + apiHost?: string; + + // Ready callback + ready?: (formo: IFormoAnalytics) => void; +} +``` + +### useFormo Hook Methods + +#### `screen(name, properties?, context?, callback?)` +Track a screen view. + +```typescript +formo.screen('Profile', { userId: '123' }); +``` + +#### `track(event, properties?, context?, callback?)` +Track a custom event. + +```typescript +formo.track('Purchase Completed', { + revenue: 99.99, + currency: 'USD', + productId: 'nft-001', +}); +``` + +#### `identify(params, properties?, context?, callback?)` +Identify a user by their wallet address. + +```typescript +formo.identify({ + address: '0x1234...', + userId: 'user-123', + providerName: 'MetaMask', + rdns: 'io.metamask', +}); +``` + +#### `connect(params, properties?, context?, callback?)` +Track wallet connection. + +```typescript +formo.connect({ + chainId: 1, + address: '0x1234...', +}); +``` + +#### `disconnect(params?, properties?, context?, callback?)` +Track wallet disconnection. + +```typescript +formo.disconnect({ + chainId: 1, + address: '0x1234...', +}); +``` + +#### `chain(params, properties?, context?, callback?)` +Track chain change. + +```typescript +formo.chain({ + chainId: 137, + address: '0x1234...', +}); +``` + +#### `signature(params, properties?, context?, callback?)` +Track signature event. + +```typescript +import { SignatureStatus } from '@formo/react-native-analytics'; + +formo.signature({ + status: SignatureStatus.CONFIRMED, + chainId: 1, + address: '0x1234...', + message: 'Sign this message', + signatureHash: '0xabcd...', +}); +``` + +#### `transaction(params, properties?, context?, callback?)` +Track transaction event. + +```typescript +import { TransactionStatus } from '@formo/react-native-analytics'; + +formo.transaction({ + status: TransactionStatus.BROADCASTED, + chainId: 1, + address: '0x1234...', + to: '0x5678...', + value: '1000000000000000000', + transactionHash: '0xdef...', +}); +``` + +#### Consent Management + +```typescript +// Opt out of tracking (GDPR compliance) +formo.optOutTracking(); + +// Check opt-out status +const isOptedOut = formo.hasOptedOutTracking(); + +// Opt back in +formo.optInTracking(); +``` + +## Event Types + +The SDK automatically enriches events with mobile-specific context: + +- Device information (OS, version, model) +- Screen dimensions and density +- Locale and timezone +- App information (if provided) +- Anonymous ID (persistent across sessions) + +## Offline Support + +Events are queued locally and sent when the device has network connectivity. Events are automatically flushed when: + +- The batch size is reached (default: 20 events) +- The flush interval passes (default: 30 seconds) +- The app goes to background + +## License + +MIT diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..e390110 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,7 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ['@babel/preset-react', { runtime: 'automatic' }], + ], +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..a10852f --- /dev/null +++ b/jest.config.js @@ -0,0 +1,30 @@ +/** @type {import('jest').Config} */ +module.exports = { + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + testMatch: ['**/__tests__/**/*.test.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], + transform: { + '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', + }, + moduleNameMapper: { + '^@formo/react-native-analytics$': '/src/index', + }, + setupFilesAfterEnv: ['/jest.setup.js'], + testEnvironment: 'node', + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/index.ts', + '!src/types/**/*', + ], + coverageThreshold: { + global: { + branches: 20, + functions: 30, + lines: 20, + statements: 20, + }, + }, + clearMocks: true, + resetMocks: true, + restoreMocks: true, +}; diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..7a3cdfb --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,121 @@ +// Jest setup file for React Native SDK tests + +// Mock React Native modules +jest.mock('react-native', () => ({ + Platform: { + OS: 'ios', + Version: '17.0', + select: jest.fn((options) => options.ios), + }, + Dimensions: { + get: jest.fn(() => ({ width: 375, height: 812, scale: 3 })), + }, + AppState: { + currentState: 'active', + addEventListener: jest.fn(() => ({ remove: jest.fn() })), + }, + NativeModules: { + RNDeviceInfo: { + deviceId: 'test-device-id', + model: 'iPhone 14', + systemName: 'iOS', + systemVersion: '17.0', + appVersion: '1.0.0', + buildNumber: '1', + bundleId: 'com.test.app', + isTablet: false, + getUniqueId: jest.fn().mockResolvedValue('test-unique-id'), + }, + }, +})); + +// Mock react-native-device-info +jest.mock('react-native-device-info', () => ({ + getUniqueId: jest.fn().mockResolvedValue('test-device-id'), + getModel: jest.fn().mockReturnValue('iPhone 14'), + getSystemName: jest.fn().mockReturnValue('iOS'), + getSystemVersion: jest.fn().mockReturnValue('17.0'), + getVersion: jest.fn().mockReturnValue('1.0.0'), + getBuildNumber: jest.fn().mockReturnValue('1'), + getBundleId: jest.fn().mockReturnValue('com.test.app'), + isTablet: jest.fn().mockReturnValue(false), + getCarrier: jest.fn().mockResolvedValue('Test Carrier'), + getDeviceName: jest.fn().mockResolvedValue('Test Device'), + getManufacturer: jest.fn().mockResolvedValue('Apple'), + getBrand: jest.fn().mockReturnValue('Apple'), + getDeviceType: jest.fn().mockReturnValue('Handset'), + hasNotch: jest.fn().mockReturnValue(true), + getDeviceId: jest.fn().mockReturnValue('iPhone14,2'), + isEmulator: jest.fn().mockResolvedValue(false), + isLandscape: jest.fn().mockResolvedValue(false), + getApplicationName: jest.fn().mockReturnValue('Test App'), + getFirstInstallTime: jest.fn().mockResolvedValue(1704067200000), + getLastUpdateTime: jest.fn().mockResolvedValue(1704067200000), + getTotalMemory: jest.fn().mockResolvedValue(6000000000), + getUsedMemory: jest.fn().mockResolvedValue(2000000000), + getUserAgent: jest.fn().mockResolvedValue('Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)'), +})); + +// Mock expo-device (optional peer dependency) +jest.mock('expo-device', () => ({ + deviceName: 'Test Device', + deviceYearClass: 2023, + isDevice: true, + brand: 'Apple', + manufacturer: 'Apple', + modelName: 'iPhone 14', + modelId: 'iPhone14,2', + osName: 'iOS', + osVersion: '17.0', + osBuildId: '21A5248v', + platformApiLevel: null, + deviceType: 1, // PHONE +}), { virtual: true }); + +// Mock expo-application (optional peer dependency) +jest.mock('expo-application', () => ({ + applicationName: 'Test App', + applicationId: 'com.test.app', + nativeApplicationVersion: '1.0.0', + nativeBuildVersion: '1', +}), { virtual: true }); + +// Mock @react-native-community/netinfo +jest.mock('@react-native-community/netinfo', () => ({ + fetch: jest.fn().mockResolvedValue({ + type: 'wifi', + isConnected: true, + isInternetReachable: true, + details: { + ssid: 'TestNetwork', + }, + }), + addEventListener: jest.fn(() => jest.fn()), +})); + +// Mock @react-native-async-storage/async-storage +jest.mock('@react-native-async-storage/async-storage', () => ({ + setItem: jest.fn().mockResolvedValue(null), + getItem: jest.fn().mockResolvedValue(null), + removeItem: jest.fn().mockResolvedValue(null), + multiGet: jest.fn().mockResolvedValue([]), + multiSet: jest.fn().mockResolvedValue(null), + multiRemove: jest.fn().mockResolvedValue(null), + getAllKeys: jest.fn().mockResolvedValue([]), + clear: jest.fn().mockResolvedValue(null), +})); + +// Global fetch mock +global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({}), +}); + +// Silence console during tests (optional - comment out for debugging) +// global.console = { +// ...console, +// log: jest.fn(), +// info: jest.fn(), +// warn: jest.fn(), +// debug: jest.fn(), +// }; diff --git a/package.json b/package.json new file mode 100644 index 0000000..8d12be5 --- /dev/null +++ b/package.json @@ -0,0 +1,123 @@ +{ + "name": "@formo/react-native-analytics", + "version": "1.0.0", + "description": "Formo Analytics SDK for React Native - Track wallet events and user analytics in mobile dApps", + "packageManager": "pnpm@9.0.0", + "repository": { + "type": "git", + "url": "git+https://github.com/getformo/sdk.git", + "directory": "packages/formo-react-native-sdk" + }, + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "types": "lib/typescript/index.d.ts", + "react-native": "src/index.ts", + "source": "src/index.ts", + "files": [ + "src", + "lib", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "exports": { + ".": { + "import": "./lib/module/index.js", + "require": "./lib/commonjs/index.js", + "types": "./lib/typescript/index.d.ts" + } + }, + "private": false, + "publishConfig": { + "access": "public", + "provenance": true + }, + "author": "Formo ", + "license": "MIT", + "keywords": [ + "react-native", + "analytics", + "crypto", + "web3", + "wallet", + "ethereum", + "wagmi", + "mobile", + "formo" + ], + "scripts": { + "build": "bob build", + "clean": "rm -rf lib", + "lint": "eslint '{src,test}/**/*.{ts,tsx}'", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": ">=1.17.0", + "@tanstack/react-query": ">=5.0.0", + "expo-application": ">=5.0.0", + "expo-device": ">=5.0.0", + "react": ">=18.0.0", + "react-native": ">=0.70.0", + "react-native-device-info": ">=10.0.0", + "wagmi": ">=2.0.0" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": false + }, + "@tanstack/react-query": { + "optional": true + }, + "expo-application": { + "optional": true + }, + "expo-device": { + "optional": true + }, + "react-native-device-info": { + "optional": true + }, + "wagmi": { + "optional": true + } + }, + "dependencies": { + "@react-native-community/netinfo": "^11.5.1", + "ethereum-cryptography": "3.2.0" + }, + "devDependencies": { + "@babel/preset-env": "^7.29.0", + "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@react-native-async-storage/async-storage": "^1.21.0", + "@react-native/babel-preset": "^0.73.0", + "@types/jest": "^29.5.12", + "@types/react": "^18.0.25", + "babel-jest": "^29.7.0", + "expo-application": "^6.0.0", + "expo-device": "^7.0.0", + "jest": "^29.7.0", + "react": "^18.3.1", + "react-native": "^0.73.0", + "react-native-builder-bob": "^0.23.0", + "react-native-device-info": "^14.0.0", + "typescript": "^5.7.3" + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "project": "tsconfig.build.json" + } + ] + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..bd46620 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9185 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@react-native-community/netinfo': + specifier: ^11.5.1 + version: 11.5.1(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + '@tanstack/react-query': + specifier: '>=5.0.0' + version: 5.90.20(react@18.3.1) + ethereum-cryptography: + specifier: 3.2.0 + version: 3.2.0 + wagmi: + specifier: '>=2.0.0' + version: 3.4.1(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@18.3.1))(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(viem@2.45.0(typescript@5.9.3)) + devDependencies: + '@babel/preset-env': + specifier: ^7.29.0 + version: 7.29.0(@babel/core@7.28.6) + '@babel/preset-react': + specifier: ^7.28.5 + version: 7.28.5(@babel/core@7.28.6) + '@babel/preset-typescript': + specifier: ^7.28.5 + version: 7.28.5(@babel/core@7.28.6) + '@react-native-async-storage/async-storage': + specifier: ^1.21.0 + version: 1.24.0(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + '@react-native/babel-preset': + specifier: ^0.73.0 + version: 0.73.21(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + '@types/jest': + specifier: ^29.5.12 + version: 29.5.14 + '@types/react': + specifier: ^18.0.25 + version: 18.3.27 + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.28.6) + expo-application: + specifier: ^6.0.0 + version: 6.1.5(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)) + expo-device: + specifier: ^7.0.0 + version: 7.1.4(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@25.1.0) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-native: + specifier: ^0.73.0 + version: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + react-native-builder-bob: + specifier: ^0.23.0 + version: 0.23.2 + react-native-device-info: + specifier: ^14.0.0 + version: 14.1.1(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + typescript: + specifier: ^5.7.3 + version: 5.9.3 + +packages: + + '@0no-co/graphql.web@1.2.0': + resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + + '@babel/code-frame@7.10.4': + resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} + + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.0': + resolution: {integrity: sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.6': + resolution: {integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.25.9': + resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-async-generator-functions@7.20.7': + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-class-properties@7.18.6': + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-decorators@7.29.0': + resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-export-default-from@7.27.1': + resolution: {integrity: sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-numeric-separator@7.18.6': + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-object-rest-spread@7.20.7': + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-optional-catch-binding@7.18.6': + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-optional-chaining@7.21.0': + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.28.6': + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-default-from@7.28.6': + resolution: {integrity: sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-flow@7.28.6': + resolution: {integrity: sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.28.6': + resolution: {integrity: sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-flow-strip-types@7.27.1': + resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.28.6': + resolution: {integrity: sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.28.5': + resolution: {integrity: sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.0': + resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-flow@7.27.1': + resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.28.5': + resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/register@7.28.6': + resolution: {integrity: sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@expo/cli@54.0.23': + resolution: {integrity: sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g==} + hasBin: true + peerDependencies: + expo: '*' + expo-router: '*' + react-native: '*' + peerDependenciesMeta: + expo-router: + optional: true + react-native: + optional: true + + '@expo/code-signing-certificates@0.0.6': + resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} + + '@expo/config-plugins@54.0.4': + resolution: {integrity: sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q==} + + '@expo/config-types@54.0.10': + resolution: {integrity: sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA==} + + '@expo/config@12.0.13': + resolution: {integrity: sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ==} + + '@expo/devcert@1.2.1': + resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} + + '@expo/devtools@0.1.8': + resolution: {integrity: sha512-SVLxbuanDjJPgc0sy3EfXUMLb/tXzp6XIHkhtPVmTWJAp+FOr6+5SeiCfJrCzZFet0Ifyke2vX3sFcKwEvCXwQ==} + peerDependencies: + react: '*' + react-native: '*' + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + + '@expo/env@2.0.8': + resolution: {integrity: sha512-5VQD6GT8HIMRaSaB5JFtOXuvfDVU80YtZIuUT/GDhUF782usIXY13Tn3IdDz1Tm/lqA9qnRZQ1BF4t7LlvdJPA==} + + '@expo/fingerprint@0.15.4': + resolution: {integrity: sha512-eYlxcrGdR2/j2M6pEDXo9zU9KXXF1vhP+V+Tl+lyY+bU8lnzrN6c637mz6Ye3em2ANy8hhUR03Raf8VsT9Ogng==} + hasBin: true + + '@expo/image-utils@0.8.8': + resolution: {integrity: sha512-HHHaG4J4nKjTtVa1GG9PCh763xlETScfEyNxxOvfTRr8IKPJckjTyqSLEtdJoFNJ1vqiABEjW7tqGhqGibZLeA==} + + '@expo/json-file@10.0.8': + resolution: {integrity: sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ==} + + '@expo/metro-config@54.0.14': + resolution: {integrity: sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA==} + peerDependencies: + expo: '*' + peerDependenciesMeta: + expo: + optional: true + + '@expo/metro@54.2.0': + resolution: {integrity: sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==} + + '@expo/osascript@2.3.8': + resolution: {integrity: sha512-/TuOZvSG7Nn0I8c+FcEaoHeBO07yu6vwDgk7rZVvAXoeAK5rkA09jRyjYsZo+0tMEFaToBeywA6pj50Mb3ny9w==} + engines: {node: '>=12'} + + '@expo/package-manager@1.9.10': + resolution: {integrity: sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA==} + + '@expo/plist@0.4.8': + resolution: {integrity: sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ==} + + '@expo/prebuild-config@54.0.8': + resolution: {integrity: sha512-EA7N4dloty2t5Rde+HP0IEE+nkAQiu4A/+QGZGT9mFnZ5KKjPPkqSyYcRvP5bhQE10D+tvz6X0ngZpulbMdbsg==} + peerDependencies: + expo: '*' + + '@expo/schema-utils@0.1.8': + resolution: {integrity: sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A==} + + '@expo/sdk-runtime-versions@1.0.0': + resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} + + '@expo/spawn-async@1.7.2': + resolution: {integrity: sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==} + engines: {node: '>=12'} + + '@expo/sudo-prompt@9.3.2': + resolution: {integrity: sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==} + + '@expo/vector-icons@15.0.3': + resolution: {integrity: sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==} + peerDependencies: + expo-font: '>=14.0.4' + react: '*' + react-native: '*' + + '@expo/ws-tunnel@1.0.6': + resolution: {integrity: sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==} + + '@expo/xcpretty@4.4.0': + resolution: {integrity: sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw==} + hasBin: true + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@isaacs/ttlcache@1.4.1': + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/create-cache-key-function@29.7.0': + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@26.6.2': + resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==} + engines: {node: '>= 10.14.2'} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.0': + resolution: {integrity: sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@react-native-async-storage/async-storage@1.24.0': + resolution: {integrity: sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==} + peerDependencies: + react-native: ^0.0.0-0 || >=0.60 <1.0 + + '@react-native-community/cli-clean@12.3.7': + resolution: {integrity: sha512-BCYW77QqyxfhiMEBOoHyciJRNV6Rhz1RvclReIKnCA9wAwmoJBeu4Mu+AwiECA2bUITX16fvPt3NwDsSd1jwfQ==} + + '@react-native-community/cli-config@12.3.7': + resolution: {integrity: sha512-IU2UhO9yj1rEBNhHWGzIXpPDzha4hizLP/PUOrhR4BUf6RVPUWEp+e1PXNGR0qjIf6esu7OC7t6mLOhH0NUJEw==} + + '@react-native-community/cli-debugger-ui@12.3.7': + resolution: {integrity: sha512-UHUFrRdcjWSCdWG9KIp2QjuRIahBQnb9epnQI7JCq6NFbFHYfEI4rI7msjMn+gG8/tSwKTV2PTPuPmZ5wWlE7Q==} + + '@react-native-community/cli-doctor@12.3.7': + resolution: {integrity: sha512-gCamZztRoAyhciuQPqdz4Xe4t3gOdNsaADNd+rva+Rx8W2PoPeNv60i7/et06wlsn6B6Sh0/hMiAftJbiHDFkg==} + + '@react-native-community/cli-hermes@12.3.7': + resolution: {integrity: sha512-ezzeiSKjRXK2+i1AAe7NhhN9CEHrgtRmTn2MAdBpE++N8fH5EQZgxFcGgGdwGvns2fm9ivyyeVnI5eAYwvM+jg==} + + '@react-native-community/cli-platform-android@12.3.7': + resolution: {integrity: sha512-mOltF3cpjNdJb3WSFwEHc1GH4ibCcnOvQ34OdWyblKy9ijuvG5SjNTlYR/UW/CURaDi3OUKAhxQMTY5d27bzGQ==} + + '@react-native-community/cli-platform-ios@12.3.7': + resolution: {integrity: sha512-2WnVsMH4ORZIhBm/5nCms1NeeKG4KarNC7PMLmrXWXB/bibDcaNsjrJiqnmCUcpTEvTQTokRfoO7Aj6NM0Cqow==} + + '@react-native-community/cli-plugin-metro@12.3.7': + resolution: {integrity: sha512-ahEw0Vfnv2Nv/jdZ2QDuGjQ9l2SczO4lXjb3ubu5vEYNLyTw3jYsLMK6iES7YQ/ApQmKdG476HU1O9uZdpaYPg==} + + '@react-native-community/cli-server-api@12.3.7': + resolution: {integrity: sha512-LYETs3CCjrLn1ZU0kYv44TywiIl5IPFHZGeXhAh2TtgOk4mo3kvXxECDil9CdO3bmDra6qyiG61KHvzr8IrHdg==} + + '@react-native-community/cli-tools@12.3.7': + resolution: {integrity: sha512-7NL/1/i+wzd4fBr/FSr3ypR05tiU/Kv9l/M1sL1c6jfcDtWXAL90R161gQkQFK7shIQ8Idp0dQX1rq49tSyfQw==} + + '@react-native-community/cli-types@12.3.7': + resolution: {integrity: sha512-NFtUMyIrNfi3A5C1cjVKDVvYHvvOF7MnOMwdD8jm2NQKewQJrehKBh1eMuykKdqhWyZmuemD4KKhL8f4FxgG0w==} + + '@react-native-community/cli@12.3.7': + resolution: {integrity: sha512-7+mOhk+3+X3BjSJZZvYrDJynA00gPYTlvT28ZjiLlbuVGfqfNiBKaxuF7rty+gjjpch4iKGvLhIhSN5cuOsdHQ==} + engines: {node: '>=18'} + hasBin: true + + '@react-native-community/netinfo@11.5.1': + resolution: {integrity: sha512-ZiLvVoNW6fIOZqU99Jdm+dfwv2T76AHifKtEDydPQOkzKg/xLIkSrOEMUigkmlNQu4MeCgFJlrapscbRzU+bKg==} + peerDependencies: + react: '*' + react-native: '>=0.59' + + '@react-native/assets-registry@0.73.1': + resolution: {integrity: sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==} + engines: {node: '>=18'} + + '@react-native/babel-plugin-codegen@0.73.4': + resolution: {integrity: sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==} + engines: {node: '>=18'} + + '@react-native/babel-plugin-codegen@0.81.5': + resolution: {integrity: sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ==} + engines: {node: '>= 20.19.4'} + + '@react-native/babel-preset@0.73.21': + resolution: {integrity: sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/babel-preset@0.81.5': + resolution: {integrity: sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA==} + engines: {node: '>= 20.19.4'} + peerDependencies: + '@babel/core': '*' + + '@react-native/codegen@0.73.3': + resolution: {integrity: sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==} + engines: {node: '>=18'} + peerDependencies: + '@babel/preset-env': ^7.1.6 + + '@react-native/codegen@0.81.5': + resolution: {integrity: sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g==} + engines: {node: '>= 20.19.4'} + peerDependencies: + '@babel/core': '*' + + '@react-native/community-cli-plugin@0.73.18': + resolution: {integrity: sha512-RN8piDh/eF+QT6YYmrj3Zd9uiaDsRY/kMT0FYR42j8/M/boE4hs4Xn0u91XzT8CAkU9q/ilyo3wJsXIJo2teww==} + engines: {node: '>=18'} + + '@react-native/debugger-frontend@0.73.3': + resolution: {integrity: sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==} + engines: {node: '>=18'} + + '@react-native/debugger-frontend@0.81.5': + resolution: {integrity: sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w==} + engines: {node: '>= 20.19.4'} + + '@react-native/dev-middleware@0.73.8': + resolution: {integrity: sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==} + engines: {node: '>=18'} + + '@react-native/dev-middleware@0.81.5': + resolution: {integrity: sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA==} + engines: {node: '>= 20.19.4'} + + '@react-native/gradle-plugin@0.73.5': + resolution: {integrity: sha512-Orrn8J/kqzEuXudl96XcZk84ZcdIpn1ojjwGSuaSQSXNcCYbOXyt0RwtW5kjCqjgSzGnOMsJNZc5FDXHVq/WzA==} + engines: {node: '>=18'} + + '@react-native/js-polyfills@0.73.1': + resolution: {integrity: sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==} + engines: {node: '>=18'} + + '@react-native/metro-babel-transformer@0.73.15': + resolution: {integrity: sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/normalize-colors@0.73.2': + resolution: {integrity: sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==} + + '@react-native/normalize-colors@0.81.5': + resolution: {integrity: sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==} + + '@react-native/virtualized-lists@0.73.4': + resolution: {integrity: sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==} + engines: {node: '>=18'} + peerDependencies: + react-native: '*' + + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/react-query@5.90.20': + resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} + peerDependencies: + react: ^18 || ^19 + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/node@25.1.0': + resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@15.0.20': + resolution: {integrity: sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@urql/core@5.2.0': + resolution: {integrity: sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==} + + '@urql/exchange-retry@1.3.2': + resolution: {integrity: sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==} + peerDependencies: + '@urql/core': ^5.0.0 + + '@wagmi/connectors@7.1.5': + resolution: {integrity: sha512-+hrb4RJywjGtUsDZNLSc4eOF+jD6pVkCZ/KFi24p993u0ymsm/kGTLXjhYx5r8Rf/cxFHEiaQaRnEfB9qyDJyw==} + peerDependencies: + '@base-org/account': ^2.5.1 + '@coinbase/wallet-sdk': ^4.3.6 + '@gemini-wallet/core': ~0.3.1 + '@metamask/sdk': ~0.33.1 + '@safe-global/safe-apps-provider': ~0.18.6 + '@safe-global/safe-apps-sdk': ^9.1.0 + '@wagmi/core': 3.3.1 + '@walletconnect/ethereum-provider': ^2.21.1 + porto: ~0.2.35 + typescript: '>=5.7.3' + viem: 2.x + peerDependenciesMeta: + '@base-org/account': + optional: true + '@coinbase/wallet-sdk': + optional: true + '@gemini-wallet/core': + optional: true + '@metamask/sdk': + optional: true + '@safe-global/safe-apps-provider': + optional: true + '@safe-global/safe-apps-sdk': + optional: true + '@walletconnect/ethereum-provider': + optional: true + porto: + optional: true + typescript: + optional: true + + '@wagmi/core@3.3.1': + resolution: {integrity: sha512-0Q8VYnVNPHe/gZsvj+Zddt8VpmKoMHXoVd887svL21QGKXEIVYiV/8R3qMv0SyC7q+GbQ5x9xezB56u3S8bWAQ==} + peerDependencies: + '@tanstack/query-core': '>=5.0.0' + ox: '>=0.11.1' + typescript: '>=5.7.3' + viem: 2.x + peerDependenciesMeta: + '@tanstack/query-core': + optional: true + ox: + optional: true + typescript: + optional: true + + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-fragments@0.2.1: + resolution: {integrity: sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + appdirsjs@1.2.7: + resolution: {integrity: sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + ast-types@0.15.2: + resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} + engines: {node: '>=4'} + + astral-regex@1.0.0: + resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} + engines: {node: '>=4'} + + async-limiter@1.0.1: + resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + + babel-core@7.0.0-bridge.0: + resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-polyfill-corejs2@0.4.15: + resolution: {integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.0: + resolution: {integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.6: + resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} + + babel-plugin-react-native-web@0.21.2: + resolution: {integrity: sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==} + + babel-plugin-syntax-hermes-parser@0.29.1: + resolution: {integrity: sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==} + + babel-plugin-transform-flow-enums@0.0.2: + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-expo@54.0.10: + resolution: {integrity: sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==} + peerDependencies: + '@babel/runtime': ^7.20.0 + expo: '*' + react-refresh: '>=0.14.0 <1.0.0' + peerDependenciesMeta: + '@babel/runtime': + optional: true + expo: + optional: true + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + hasBin: true + + better-opn@3.0.2: + resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} + engines: {node: '>=12.0.0'} + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bplist-creator@0.1.0: + resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} + + bplist-parser@0.3.1: + resolution: {integrity: sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==} + engines: {node: '>= 5.10.0'} + + bplist-parser@0.3.2: + resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} + engines: {node: '>= 5.10.0'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + caller-callsite@2.0.0: + resolution: {integrity: sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==} + engines: {node: '>=4'} + + caller-path@2.0.0: + resolution: {integrity: sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==} + engines: {node: '>=4'} + + callsites@2.0.0: + resolution: {integrity: sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==} + engines: {node: '>=4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-edge-launcher@0.2.0: + resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} + + chromium-edge-launcher@1.0.0: + resolution: {integrity: sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==} + + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@2.1.0: + resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + engines: {node: '>=4'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@5.2.1: + resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} + engines: {node: '>=4'} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + + dedent@1.7.1: + resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + del@6.1.1: + resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} + engines: {node: '>=10'} + + denodeify@1.2.1: + resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + deprecated-react-native-prop-types@5.0.0: + resolution: {integrity: sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==} + engines: {node: '>=18'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.279: + resolution: {integrity: sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-editor@0.4.2: + resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} + engines: {node: '>=8'} + + envinfo@7.21.0: + resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} + engines: {node: '>=4'} + hasBin: true + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + errorhandler@1.5.2: + resolution: {integrity: sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==} + engines: {node: '>= 0.8'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + ethereum-cryptography@3.2.0: + resolution: {integrity: sha512-Urr5YVsalH+Jo0sYkTkv1MyI9bLYZwW8BENZCeE1QYaTHETEYx0Nv/SVsWkSqpYrzweg6d8KMY1wTjH/1m/BIg==} + engines: {node: ^14.21.3 || >=16, npm: '>=9'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + exec-async@2.2.0: + resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==} + + execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + expo-application@6.1.5: + resolution: {integrity: sha512-ToImFmzw8luY043pWFJhh2ZMm4IwxXoHXxNoGdlhD4Ym6+CCmkAvCglg0FK8dMLzAb+/XabmOE7Rbm8KZb6NZg==} + peerDependencies: + expo: '*' + + expo-asset@12.0.12: + resolution: {integrity: sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-constants@18.0.13: + resolution: {integrity: sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-device@7.1.4: + resolution: {integrity: sha512-HS04IiE1Fy0FRjBLurr9e5A6yj3kbmQB+2jCZvbSGpsjBnCLdSk/LCii4f5VFhPIBWJLyYuN5QqJyEAw6BcS4Q==} + peerDependencies: + expo: '*' + + expo-file-system@19.0.21: + resolution: {integrity: sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-font@14.0.11: + resolution: {integrity: sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-keep-awake@15.0.8: + resolution: {integrity: sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ==} + peerDependencies: + expo: '*' + react: '*' + + expo-modules-autolinking@3.0.24: + resolution: {integrity: sha512-TP+6HTwhL7orDvsz2VzauyQlXJcAWyU3ANsZ7JGL4DQu8XaZv/A41ZchbtAYLfozNA2Ya1Hzmhx65hXryBMjaQ==} + hasBin: true + + expo-modules-core@3.0.29: + resolution: {integrity: sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q==} + peerDependencies: + react: '*' + react-native: '*' + + expo-server@1.0.5: + resolution: {integrity: sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA==} + engines: {node: '>=20.16.0'} + + expo@54.0.33: + resolution: {integrity: sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==} + hasBin: true + peerDependencies: + '@expo/dom-webview': '*' + '@expo/metro-runtime': '*' + react: '*' + react-native: '*' + react-native-webview: '*' + peerDependenciesMeta: + '@expo/dom-webview': + optional: true + '@expo/metro-runtime': + optional: true + react-native-webview: + optional: true + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-xml-parser@4.5.3: + resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} + hasBin: true + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + + flow-parser@0.206.0: + resolution: {integrity: sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==} + engines: {node: '>=0.4.0'} + + fontfaceobserver@2.3.0: + resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} + + freeport-async@2.0.0: + resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==} + engines: {node: '>=8'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + getenv@2.0.0: + resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} + engines: {node: '>=6'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@13.0.1: + resolution: {integrity: sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==} + engines: {node: 20 || >=22} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + global-dirs@0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hermes-estree@0.15.0: + resolution: {integrity: sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==} + + hermes-estree@0.23.1: + resolution: {integrity: sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==} + + hermes-estree@0.29.1: + resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} + + hermes-estree@0.32.0: + resolution: {integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==} + + hermes-parser@0.15.0: + resolution: {integrity: sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==} + + hermes-parser@0.23.1: + resolution: {integrity: sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==} + + hermes-parser@0.29.1: + resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} + + hermes-parser@0.32.0: + resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} + + hermes-profile-transformer@0.0.6: + resolution: {integrity: sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==} + engines: {node: '>=8'} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + image-size@1.2.1: + resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} + engines: {node: '>=16.x'} + hasBin: true + + import-fresh@2.0.0: + resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} + engines: {node: '>=4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-directory@0.3.1: + resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} + engines: {node: '>=0.10.0'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-git-dirty@2.0.2: + resolution: {integrity: sha512-U3YCo+GKR/rDsY7r0v/LBICbQwsx859tDQnAT+v0E/zCDeWbQ1TUt1FtyExeyik7VIJlYOLHCIifLdz71HDalg==} + engines: {node: '>=10'} + + is-git-repository@2.0.0: + resolution: {integrity: sha512-HDO50CG5suIAcmqG4F1buqVXEZRPn+RaXIn9pFKq/947FBo2bCRwK7ZluEVZOy99a4IQyqsjbKEpAiOXCccOHQ==} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + is-wsl@1.1.0: + resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} + engines: {node: '>=4'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jimp-compact@0.16.1: + resolution: {integrity: sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==} + + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsc-android@250231.0.0: + resolution: {integrity: sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==} + + jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + + jscodeshift@0.14.0: + resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} + hasBin: true + peerDependencies: + '@babel/preset-env': ^7.1.6 + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lan-network@0.1.7: + resolution: {integrity: sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==} + hasBin: true + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lightningcss-android-arm64@1.31.1: + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.31.1: + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.31.1: + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.31.1: + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.31.1: + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.31.1: + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.31.1: + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.31.1: + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.31.1: + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.31.1: + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.31.1: + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.31.1: + resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + log-symbols@2.2.0: + resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} + engines: {node: '>=4'} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + logkitty@0.7.1: + resolution: {integrity: sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==} + hasBin: true + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.5: + resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + merge-options@3.0.4: + resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} + engines: {node: '>=10'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + metro-babel-transformer@0.80.12: + resolution: {integrity: sha512-YZziRs0MgA3pzCkkvOoQRXjIoVjvrpi/yRlJnObyIvMP6lFdtyG4nUGIwGY9VXnBvxmXD6mPY2e+NSw6JAyiRg==} + engines: {node: '>=18'} + + metro-babel-transformer@0.83.3: + resolution: {integrity: sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==} + engines: {node: '>=20.19.4'} + + metro-cache-key@0.80.12: + resolution: {integrity: sha512-o4BspKnugg/pE45ei0LGHVuBJXwRgruW7oSFAeSZvBKA/sGr0UhOGY3uycOgWInnS3v5yTTfiBA9lHlNRhsvGA==} + engines: {node: '>=18'} + + metro-cache-key@0.83.3: + resolution: {integrity: sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==} + engines: {node: '>=20.19.4'} + + metro-cache@0.80.12: + resolution: {integrity: sha512-p5kNHh2KJ0pbQI/H7ZBPCEwkyNcSz7OUkslzsiIWBMPQGFJ/xArMwkV7I+GJcWh+b4m6zbLxE5fk6fqbVK1xGA==} + engines: {node: '>=18'} + + metro-cache@0.83.3: + resolution: {integrity: sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==} + engines: {node: '>=20.19.4'} + + metro-config@0.80.12: + resolution: {integrity: sha512-4rwOWwrhm62LjB12ytiuR5NgK1ZBNr24/He8mqCsC+HXZ+ATbrewLNztzbAZHtFsrxP4D4GLTGgh96pCpYLSAQ==} + engines: {node: '>=18'} + + metro-config@0.83.3: + resolution: {integrity: sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==} + engines: {node: '>=20.19.4'} + + metro-core@0.80.12: + resolution: {integrity: sha512-QqdJ/yAK+IpPs2HU/h5v2pKEdANBagSsc6DRSjnwSyJsCoHlmyJKCaCJ7KhWGx+N4OHxh37hoA8fc2CuZbx0Fw==} + engines: {node: '>=18'} + + metro-core@0.83.3: + resolution: {integrity: sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==} + engines: {node: '>=20.19.4'} + + metro-file-map@0.80.12: + resolution: {integrity: sha512-sYdemWSlk66bWzW2wp79kcPMzwuG32x1ZF3otI0QZTmrnTaaTiGyhE66P1z6KR4n2Eu5QXiABa6EWbAQv0r8bw==} + engines: {node: '>=18'} + + metro-file-map@0.83.3: + resolution: {integrity: sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==} + engines: {node: '>=20.19.4'} + + metro-minify-terser@0.80.12: + resolution: {integrity: sha512-muWzUw3y5k+9083ZoX9VaJLWEV2Jcgi+Oan0Mmb/fBNMPqP9xVDuy4pOMn/HOiGndgfh/MK7s4bsjkyLJKMnXQ==} + engines: {node: '>=18'} + + metro-minify-terser@0.83.3: + resolution: {integrity: sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==} + engines: {node: '>=20.19.4'} + + metro-resolver@0.80.12: + resolution: {integrity: sha512-PR24gYRZnYHM3xT9pg6BdbrGbM/Cu1TcyIFBVlAk7qDAuHkUNQ1nMzWumWs+kwSvtd9eZGzHoucGJpTUEeLZAw==} + engines: {node: '>=18'} + + metro-resolver@0.83.3: + resolution: {integrity: sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==} + engines: {node: '>=20.19.4'} + + metro-runtime@0.80.12: + resolution: {integrity: sha512-LIx7+92p5rpI0i6iB4S4GBvvLxStNt6fF0oPMaUd1Weku7jZdfkCZzmrtDD9CSQ6EPb0T9NUZoyXIxlBa3wOCw==} + engines: {node: '>=18'} + + metro-runtime@0.83.3: + resolution: {integrity: sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==} + engines: {node: '>=20.19.4'} + + metro-source-map@0.80.12: + resolution: {integrity: sha512-o+AXmE7hpvM8r8MKsx7TI21/eerYYy2DCDkWfoBkv+jNkl61khvDHlQn0cXZa6lrcNZiZkl9oHSMcwLLIrFmpw==} + engines: {node: '>=18'} + + metro-source-map@0.83.3: + resolution: {integrity: sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==} + engines: {node: '>=20.19.4'} + + metro-symbolicate@0.80.12: + resolution: {integrity: sha512-/dIpNdHksXkGHZXARZpL7doUzHqSNxgQ8+kQGxwpJuHnDhGkENxB5PS2QBaTDdEcmyTMjS53CN1rl9n1gR6fmw==} + engines: {node: '>=18'} + hasBin: true + + metro-symbolicate@0.83.3: + resolution: {integrity: sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==} + engines: {node: '>=20.19.4'} + hasBin: true + + metro-transform-plugins@0.80.12: + resolution: {integrity: sha512-WQWp00AcZvXuQdbjQbx1LzFR31IInlkCDYJNRs6gtEtAyhwpMMlL2KcHmdY+wjDO9RPcliZ+Xl1riOuBecVlPA==} + engines: {node: '>=18'} + + metro-transform-plugins@0.83.3: + resolution: {integrity: sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==} + engines: {node: '>=20.19.4'} + + metro-transform-worker@0.80.12: + resolution: {integrity: sha512-KAPFN1y3eVqEbKLx1I8WOarHPqDMUa8WelWxaJCNKO/yHCP26zELeqTJvhsQup+8uwB6EYi/sp0b6TGoh6lOEA==} + engines: {node: '>=18'} + + metro-transform-worker@0.83.3: + resolution: {integrity: sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==} + engines: {node: '>=20.19.4'} + + metro@0.80.12: + resolution: {integrity: sha512-1UsH5FzJd9quUsD1qY+zUG4JY3jo3YEMxbMYH9jT6NK3j4iORhlwTK8fYTfAUBhDKjgLfKjAh7aoazNE23oIRA==} + engines: {node: '>=18'} + hasBin: true + + metro@0.83.3: + resolution: {integrity: sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==} + engines: {node: '>=20.19.4'} + hasBin: true + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@1.2.0: + resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + engines: {node: '>=4'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mipd@0.0.7: + resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nested-error-stacks@2.0.1: + resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + + nocache@3.0.4: + resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} + engines: {node: '>=12.0.0'} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-dir@0.1.17: + resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} + engines: {node: '>= 0.10.5'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-forge@1.3.3: + resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} + engines: {node: '>= 6.13.0'} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + node-stream-zip@1.15.0: + resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} + engines: {node: '>=0.12.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + + ob1@0.80.12: + resolution: {integrity: sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==} + engines: {node: '>=18'} + + ob1@0.83.3: + resolution: {integrity: sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==} + engines: {node: '>=20.19.4'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@2.0.1: + resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + engines: {node: '>=4'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@6.4.0: + resolution: {integrity: sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==} + engines: {node: '>=8'} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + ora@3.4.0: + resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} + engines: {node: '>=6'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + ox@0.11.3: + resolution: {integrity: sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-png@2.1.0: + resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} + engines: {node: '>=10'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@3.0.1: + resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} + engines: {node: '>=10'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} + + pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + pretty-format@26.6.2: + resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==} + engines: {node: '>= 10'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qrcode-terminal@0.11.0: + resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} + hasBin: true + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-devtools-core@4.28.5: + resolution: {integrity: sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==} + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-native-builder-bob@0.23.2: + resolution: {integrity: sha512-ehv2XKS9cvPR5JR7FIpSx3qY7tULkljT2Kb82FBAxXsFLjqlRU1WfqHRLh6lytL2XqAxLQODpPfHUH53SsXnag==} + engines: {node: '>= 18.0.0'} + hasBin: true + + react-native-device-info@14.1.1: + resolution: {integrity: sha512-lXFpe6DJmzbQXNLWxlMHP2xuTU5gwrKAvI8dCAZuERhW9eOXSubOQIesk9lIBnsi9pI19GMrcpJEvs4ARPRYmw==} + peerDependencies: + react-native: '*' + + react-native@0.73.11: + resolution: {integrity: sha512-yvQIX+ZXOHMFnhmwZ1fBpRI/53k+iLN8DxVf24Fx4ABU63RGAYfyCZC0/3W+5OUVx4KSIZUv4Tv+/NGIieBOwg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + react: 18.2.0 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-shallow-renderer@16.15.0: + resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readline@1.3.0: + resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==} + + recast@0.21.5: + resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} + engines: {node: '>= 4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requireg@0.2.2: + resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==} + engines: {node: '>= 4.0.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@3.0.0: + resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} + engines: {node: '>=4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-global@1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + + resolve-workspace-root@2.0.1: + resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@1.7.1: + resolution: {integrity: sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==} + + restore-cursor@2.0.0: + resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + engines: {node: '>=4'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@2.6.3: + resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + + scheduler@0.24.0-canary-efb381bbf-20230505: + resolution: {integrity: sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serialize-error@2.1.0: + resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + engines: {node: '>=0.10.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-plist@1.3.1: + resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@2.1.0: + resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} + engines: {node: '>=6'} + + slugify@1.6.6: + resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} + engines: {node: '>=8.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-buffers@2.2.0: + resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} + engines: {node: '>= 0.10.0'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@1.1.2: + resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + + structured-headers@0.4.1: + resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + sudo-prompt@9.2.1: + resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-hyperlinks@2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tar@7.5.7: + resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + engines: {node: '>=18'} + + temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + + temp@0.8.4: + resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} + engines: {node: '>=6.0.0'} + + terminal-link@2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ua-parser-js@0.7.41: + resolution: {integrity: sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==} + hasBin: true + + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@6.23.0: + resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} + engines: {node: '>=18.17'} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@7.0.3: + resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + hasBin: true + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + viem@2.45.0: + resolution: {integrity: sha512-iVA9qrAgRdtpWa80lCZ6Jri6XzmLOwwA1wagX2HnKejKeliFLpON0KOdyfqvcy+gUpBVP59LBxP2aKiL3aj8fg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + + wagmi@3.4.1: + resolution: {integrity: sha512-v6svxWxfIqV82lXNclOMn+h0SYCtXtxf0HWCwyjIJPZH1SR7yRqyQguWUDQtzvNSefFQEoCk+MVOX9nTR5d4Zw==} + peerDependencies: + '@tanstack/react-query': '>=5.0.0' + react: '>=18' + typescript: '>=5.7.3' + viem: 2.x + peerDependenciesMeta: + typescript: + optional: true + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@5.0.0: + resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} + engines: {node: '>=8'} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url-without-unicode@8.0.0-3: + resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==} + engines: {node: '>=10'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wonka@6.3.5: + resolution: {integrity: sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@2.4.3: + resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@6.2.3: + resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xcode@3.0.1: + resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} + engines: {node: '>=10.0.0'} + + xml2js@0.6.0: + resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zustand@5.0.0: + resolution: {integrity: sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@0no-co/graphql.web@1.2.0': {} + + '@adraffy/ens-normalize@1.11.1': {} + + '@babel/code-frame@7.10.4': + dependencies: + '@babel/highlight': 7.25.9 + + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@7.29.0': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.6 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.6 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@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 + transitivePeerDependencies: + - supports-color + + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.28.6 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.6 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/highlight@7.25.9': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + + '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6) + + '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.28.6)': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + + '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6) + + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-export-default-from@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-generator-functions@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.28.6) + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.28.6) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.6) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.6) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.28.6 + '@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/core@7.28.6) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.28.6) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.6) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.28.6) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.28.6) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-flow@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.6) + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.28.6 + esutils: 2.0.3 + + '@babel/preset-react@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/register@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@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.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@expo/cli@54.0.23(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))': + dependencies: + '@0no-co/graphql.web': 1.2.0 + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 12.0.13 + '@expo/config-plugins': 54.0.4 + '@expo/devcert': 1.2.1 + '@expo/env': 2.0.8 + '@expo/image-utils': 0.8.8 + '@expo/json-file': 10.0.8 + '@expo/metro': 54.2.0 + '@expo/metro-config': 54.0.14(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)) + '@expo/osascript': 2.3.8 + '@expo/package-manager': 1.9.10 + '@expo/plist': 0.4.8 + '@expo/prebuild-config': 54.0.8(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)) + '@expo/schema-utils': 0.1.8 + '@expo/spawn-async': 1.7.2 + '@expo/ws-tunnel': 1.0.6 + '@expo/xcpretty': 4.4.0 + '@react-native/dev-middleware': 0.81.5 + '@urql/core': 5.2.0 + '@urql/exchange-retry': 1.3.2(@urql/core@5.2.0) + accepts: 1.3.8 + arg: 5.0.2 + better-opn: 3.0.2 + bplist-creator: 0.1.0 + bplist-parser: 0.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + compression: 1.8.1 + connect: 3.7.0 + debug: 4.4.3 + env-editor: 0.4.2 + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + expo-server: 1.0.5 + freeport-async: 2.0.0 + getenv: 2.0.0 + glob: 13.0.1 + lan-network: 0.1.7 + minimatch: 9.0.5 + node-forge: 1.3.3 + npm-package-arg: 11.0.3 + ora: 3.4.0 + picomatch: 3.0.1 + pretty-bytes: 5.6.0 + pretty-format: 29.7.0 + progress: 2.0.3 + prompts: 2.4.2 + qrcode-terminal: 0.11.0 + require-from-string: 2.0.2 + requireg: 0.2.2 + resolve: 1.22.11 + resolve-from: 5.0.0 + resolve.exports: 2.0.3 + semver: 7.7.3 + send: 0.19.2 + slugify: 1.6.6 + source-map-support: 0.5.21 + stacktrace-parser: 0.1.11 + structured-headers: 0.4.1 + tar: 7.5.7 + terminal-link: 2.1.1 + undici: 6.23.0 + wrap-ansi: 7.0.0 + ws: 8.18.3 + optionalDependencies: + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + transitivePeerDependencies: + - bufferutil + - graphql + - supports-color + - utf-8-validate + + '@expo/code-signing-certificates@0.0.6': + dependencies: + node-forge: 1.3.3 + + '@expo/config-plugins@54.0.4': + dependencies: + '@expo/config-types': 54.0.10 + '@expo/json-file': 10.0.8 + '@expo/plist': 0.4.8 + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.1 + resolve-from: 5.0.0 + semver: 7.7.3 + slash: 3.0.0 + slugify: 1.6.6 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + + '@expo/config-types@54.0.10': {} + + '@expo/config@12.0.13': + dependencies: + '@babel/code-frame': 7.10.4 + '@expo/config-plugins': 54.0.4 + '@expo/config-types': 54.0.10 + '@expo/json-file': 10.0.8 + deepmerge: 4.3.1 + getenv: 2.0.0 + glob: 13.0.1 + require-from-string: 2.0.2 + resolve-from: 5.0.0 + resolve-workspace-root: 2.0.1 + semver: 7.7.3 + slugify: 1.6.6 + sucrase: 3.35.1 + transitivePeerDependencies: + - supports-color + + '@expo/devcert@1.2.1': + dependencies: + '@expo/sudo-prompt': 9.3.2 + debug: 3.2.7 + transitivePeerDependencies: + - supports-color + + '@expo/devtools@0.1.8(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)': + dependencies: + chalk: 4.1.2 + optionalDependencies: + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + '@expo/env@2.0.8': + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + dotenv: 16.4.7 + dotenv-expand: 11.0.7 + getenv: 2.0.0 + transitivePeerDependencies: + - supports-color + + '@expo/fingerprint@0.15.4': + dependencies: + '@expo/spawn-async': 1.7.2 + arg: 5.0.2 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.1 + ignore: 5.3.2 + minimatch: 9.0.5 + p-limit: 3.1.0 + resolve-from: 5.0.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + '@expo/image-utils@0.8.8': + dependencies: + '@expo/spawn-async': 1.7.2 + chalk: 4.1.2 + getenv: 2.0.0 + jimp-compact: 0.16.1 + parse-png: 2.1.0 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + semver: 7.7.3 + temp-dir: 2.0.0 + unique-string: 2.0.0 + + '@expo/json-file@10.0.8': + dependencies: + '@babel/code-frame': 7.10.4 + json5: 2.2.3 + + '@expo/metro-config@54.0.14(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@expo/config': 12.0.13 + '@expo/env': 2.0.8 + '@expo/json-file': 10.0.8 + '@expo/metro': 54.2.0 + '@expo/spawn-async': 1.7.2 + browserslist: 4.28.1 + chalk: 4.1.2 + debug: 4.4.3 + dotenv: 16.4.7 + dotenv-expand: 11.0.7 + getenv: 2.0.0 + glob: 13.0.1 + hermes-parser: 0.29.1 + jsc-safe-url: 0.2.4 + lightningcss: 1.31.1 + minimatch: 9.0.5 + postcss: 8.4.49 + resolve-from: 5.0.0 + optionalDependencies: + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@expo/metro@54.2.0': + dependencies: + metro: 0.83.3 + 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-minify-terser: 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 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@expo/osascript@2.3.8': + dependencies: + '@expo/spawn-async': 1.7.2 + exec-async: 2.2.0 + + '@expo/package-manager@1.9.10': + dependencies: + '@expo/json-file': 10.0.8 + '@expo/spawn-async': 1.7.2 + chalk: 4.1.2 + npm-package-arg: 11.0.3 + ora: 3.4.0 + resolve-workspace-root: 2.0.1 + + '@expo/plist@0.4.8': + dependencies: + '@xmldom/xmldom': 0.8.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + '@expo/prebuild-config@54.0.8(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))': + dependencies: + '@expo/config': 12.0.13 + '@expo/config-plugins': 54.0.4 + '@expo/config-types': 54.0.10 + '@expo/image-utils': 0.8.8 + '@expo/json-file': 10.0.8 + '@react-native/normalize-colors': 0.81.5 + debug: 4.4.3 + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + resolve-from: 5.0.0 + semver: 7.7.3 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + + '@expo/schema-utils@0.1.8': {} + + '@expo/sdk-runtime-versions@1.0.0': {} + + '@expo/spawn-async@1.7.2': + dependencies: + cross-spawn: 7.0.6 + + '@expo/sudo-prompt@9.3.2': {} + + '@expo/vector-icons@15.0.3(expo-font@14.0.11(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)': + dependencies: + expo-font: 14.0.11(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + '@expo/ws-tunnel@1.0.6': {} + + '@expo/xcpretty@4.4.0': + dependencies: + '@babel/code-frame': 7.28.6 + chalk: 4.1.2 + js-yaml: 4.1.1 + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.1': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@isaacs/ttlcache@1.4.1': {} + + '@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.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@25.1.0) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/create-cache-key-function@29.7.0': + dependencies: + '@jest/types': 29.6.3 + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 25.1.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 25.1.0 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.28.6 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@26.6.2': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.1.0 + '@types/yargs': 15.0.20 + chalk: 4.1.2 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.1.0 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.0': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@react-native-async-storage/async-storage@1.24.0(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))': + dependencies: + merge-options: 3.0.4 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + '@react-native-community/cli-clean@12.3.7': + dependencies: + '@react-native-community/cli-tools': 12.3.7 + chalk: 4.1.2 + execa: 5.1.1 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-config@12.3.7': + dependencies: + '@react-native-community/cli-tools': 12.3.7 + chalk: 4.1.2 + cosmiconfig: 5.2.1 + deepmerge: 4.3.1 + glob: 7.2.3 + joi: 17.13.3 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-debugger-ui@12.3.7': + dependencies: + serve-static: 1.16.3 + transitivePeerDependencies: + - supports-color + + '@react-native-community/cli-doctor@12.3.7': + dependencies: + '@react-native-community/cli-config': 12.3.7 + '@react-native-community/cli-platform-android': 12.3.7 + '@react-native-community/cli-platform-ios': 12.3.7 + '@react-native-community/cli-tools': 12.3.7 + chalk: 4.1.2 + command-exists: 1.2.9 + deepmerge: 4.3.1 + envinfo: 7.21.0 + execa: 5.1.1 + hermes-profile-transformer: 0.0.6 + node-stream-zip: 1.15.0 + ora: 5.4.1 + semver: 7.7.3 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + yaml: 2.8.2 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-hermes@12.3.7': + dependencies: + '@react-native-community/cli-platform-android': 12.3.7 + '@react-native-community/cli-tools': 12.3.7 + chalk: 4.1.2 + hermes-profile-transformer: 0.0.6 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-platform-android@12.3.7': + dependencies: + '@react-native-community/cli-tools': 12.3.7 + chalk: 4.1.2 + execa: 5.1.1 + fast-xml-parser: 4.5.3 + glob: 7.2.3 + logkitty: 0.7.1 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-platform-ios@12.3.7': + dependencies: + '@react-native-community/cli-tools': 12.3.7 + chalk: 4.1.2 + execa: 5.1.1 + fast-xml-parser: 4.5.3 + glob: 7.2.3 + ora: 5.4.1 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-plugin-metro@12.3.7': {} + + '@react-native-community/cli-server-api@12.3.7': + dependencies: + '@react-native-community/cli-debugger-ui': 12.3.7 + '@react-native-community/cli-tools': 12.3.7 + compression: 1.8.1 + connect: 3.7.0 + errorhandler: 1.5.2 + nocache: 3.0.4 + pretty-format: 26.6.2 + serve-static: 1.16.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@react-native-community/cli-tools@12.3.7': + dependencies: + appdirsjs: 1.2.7 + chalk: 4.1.2 + find-up: 5.0.0 + mime: 2.6.0 + node-fetch: 2.7.0 + open: 6.4.0 + ora: 5.4.1 + semver: 7.7.3 + shell-quote: 1.8.3 + sudo-prompt: 9.2.1 + transitivePeerDependencies: + - encoding + + '@react-native-community/cli-types@12.3.7': + dependencies: + joi: 17.13.3 + + '@react-native-community/cli@12.3.7': + dependencies: + '@react-native-community/cli-clean': 12.3.7 + '@react-native-community/cli-config': 12.3.7 + '@react-native-community/cli-debugger-ui': 12.3.7 + '@react-native-community/cli-doctor': 12.3.7 + '@react-native-community/cli-hermes': 12.3.7 + '@react-native-community/cli-plugin-metro': 12.3.7 + '@react-native-community/cli-server-api': 12.3.7 + '@react-native-community/cli-tools': 12.3.7 + '@react-native-community/cli-types': 12.3.7 + chalk: 4.1.2 + commander: 9.5.0 + deepmerge: 4.3.1 + execa: 5.1.1 + find-up: 4.1.0 + fs-extra: 8.1.0 + graceful-fs: 4.2.11 + prompts: 2.4.2 + semver: 7.7.3 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@react-native-community/netinfo@11.5.1(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + '@react-native/assets-registry@0.73.1': {} + + '@react-native/babel-plugin-codegen@0.73.4(@babel/preset-env@7.29.0(@babel/core@7.28.6))': + dependencies: + '@react-native/codegen': 0.73.3(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.6)': + dependencies: + '@babel/traverse': 7.28.6 + '@react-native/codegen': 0.81.5(@babel/core@7.28.6) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/babel-preset@0.73.21(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.28.6) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.28.6) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.28.6) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-export-default-from': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.6) + '@babel/template': 7.28.6 + '@react-native/babel-plugin-codegen': 0.73.4(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.6) + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/babel-preset@0.81.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-export-default-from': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-async-generator-functions': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-regenerator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.6) + '@babel/template': 7.28.6 + '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.28.6) + babel-plugin-syntax-hermes-parser: 0.29.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.6) + react-refresh: 0.14.2 + transitivePeerDependencies: + - supports-color + + '@react-native/codegen@0.73.3(@babel/preset-env@7.29.0(@babel/core@7.28.6))': + dependencies: + '@babel/parser': 7.28.6 + '@babel/preset-env': 7.29.0(@babel/core@7.28.6) + flow-parser: 0.206.0 + glob: 7.2.3 + invariant: 2.2.4 + jscodeshift: 0.14.0(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + mkdirp: 0.5.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + '@react-native/codegen@0.81.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + glob: 7.2.3 + hermes-parser: 0.29.1 + invariant: 2.2.4 + nullthrows: 1.1.1 + yargs: 17.7.2 + + '@react-native/community-cli-plugin@0.73.18(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))': + dependencies: + '@react-native-community/cli-server-api': 12.3.7 + '@react-native-community/cli-tools': 12.3.7 + '@react-native/dev-middleware': 0.73.8 + '@react-native/metro-babel-transformer': 0.73.15(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + chalk: 4.1.2 + execa: 5.1.1 + metro: 0.80.12 + metro-config: 0.80.12 + metro-core: 0.80.12 + node-fetch: 2.7.0 + readline: 1.3.0 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.73.3': {} + + '@react-native/debugger-frontend@0.81.5': {} + + '@react-native/dev-middleware@0.73.8': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.73.3 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 1.0.0 + connect: 3.7.0 + debug: 2.6.9 + node-fetch: 2.7.0 + open: 7.4.2 + serve-static: 1.16.3 + temp-dir: 2.0.0 + ws: 6.2.3 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@react-native/dev-middleware@0.81.5': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.81.5 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.2.0 + connect: 3.7.0 + debug: 4.4.3 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 6.2.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/gradle-plugin@0.73.5': {} + + '@react-native/js-polyfills@0.73.1': {} + + '@react-native/metro-babel-transformer@0.73.15(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))': + dependencies: + '@babel/core': 7.28.6 + '@react-native/babel-preset': 0.73.21(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + hermes-parser: 0.15.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/normalize-colors@0.73.2': {} + + '@react-native/normalize-colors@0.81.5': {} + + '@react-native/virtualized-lists@0.73.4(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + '@scure/base@1.2.6': {} + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@tanstack/query-core@5.90.20': {} + + '@tanstack/react-query@5.90.20(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 18.3.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 25.1.0 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/node@25.1.0': + dependencies: + undici-types: 7.16.0 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.15': {} + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@15.0.20': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@ungap/structured-clone@1.3.0': {} + + '@urql/core@5.2.0': + dependencies: + '@0no-co/graphql.web': 1.2.0 + wonka: 6.3.5 + transitivePeerDependencies: + - graphql + + '@urql/exchange-retry@1.3.2(@urql/core@5.2.0)': + dependencies: + '@urql/core': 5.2.0 + wonka: 6.3.5 + + '@wagmi/connectors@7.1.5(@wagmi/core@3.3.1(@tanstack/query-core@5.90.20)(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.45.0(typescript@5.9.3)))(typescript@5.9.3)(viem@2.45.0(typescript@5.9.3))': + dependencies: + '@wagmi/core': 3.3.1(@tanstack/query-core@5.90.20)(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.45.0(typescript@5.9.3)) + viem: 2.45.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@wagmi/core@3.3.1(@tanstack/query-core@5.90.20)(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.45.0(typescript@5.9.3))': + dependencies: + eventemitter3: 5.0.1 + mipd: 0.0.7(typescript@5.9.3) + viem: 2.45.0(typescript@5.9.3) + zustand: 5.0.0(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + optionalDependencies: + '@tanstack/query-core': 5.90.20 + ox: 0.11.3(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/react' + - immer + - react + - use-sync-external-store + + '@xmldom/xmldom@0.8.11': {} + + abitype@1.2.3(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + anser@1.4.10: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-fragments@0.2.1: + dependencies: + colorette: 1.4.0 + slice-ansi: 2.1.0 + strip-ansi: 5.2.0 + + ansi-regex@4.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + appdirsjs@1.2.7: {} + + arg@5.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + asap@2.0.6: {} + + ast-types@0.15.2: + dependencies: + tslib: 2.8.1 + + astral-regex@1.0.0: {} + + async-limiter@1.0.1: {} + + babel-core@7.0.0-bridge.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + + babel-jest@29.7.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.28.6) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.28.6): + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + babel-plugin-react-compiler@1.0.0: + dependencies: + '@babel/types': 7.28.6 + + babel-plugin-react-native-web@0.21.2: {} + + babel-plugin-syntax-hermes-parser@0.29.1: + dependencies: + hermes-parser: 0.29.1 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.28.6): + dependencies: + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - '@babel/core' + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.6) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.6) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.6) + + babel-preset-expo@54.0.10(@babel/core@7.28.6)(@babel/runtime@7.28.6)(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-refresh@0.14.2): + dependencies: + '@babel/helper-module-imports': 7.28.6 + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-syntax-export-default-from': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.6) + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) + '@react-native/babel-preset': 0.81.5(@babel/core@7.28.6) + babel-plugin-react-compiler: 1.0.0 + babel-plugin-react-native-web: 0.21.2 + babel-plugin-syntax-hermes-parser: 0.29.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.6) + debug: 4.4.3 + react-refresh: 0.14.2 + resolve-from: 5.0.0 + optionalDependencies: + '@babel/runtime': 7.28.6 + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + babel-preset-jest@29.6.3(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.19: {} + + better-opn@3.0.2: + dependencies: + open: 8.4.2 + + big-integer@1.6.52: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bplist-creator@0.1.0: + dependencies: + stream-buffers: 2.2.0 + + bplist-parser@0.3.1: + dependencies: + big-integer: 1.6.52 + + bplist-parser@0.3.2: + dependencies: + big-integer: 1.6.52 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.279 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + caller-callsite@2.0.0: + dependencies: + callsites: 2.0.0 + + caller-path@2.0.0: + dependencies: + caller-callsite: 2.0.0 + + callsites@2.0.0: {} + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001766: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + chownr@3.0.0: {} + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 25.1.0 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@0.2.0: + dependencies: + '@types/node': 25.1.0 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@1.0.0: + dependencies: + '@types/node': 25.1.0 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + clean-stack@2.2.0: {} + + cli-cursor@2.1.0: + dependencies: + restore-cursor: 2.0.0 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + clone@1.0.4: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + colorette@1.4.0: {} + + command-exists@1.2.9: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + commander@9.5.0: {} + + commondir@1.0.1: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + convert-source-map@2.0.0: {} + + core-js-compat@3.48.0: + dependencies: + browserslist: 4.28.1 + + core-util-is@1.0.3: {} + + cosmiconfig@5.2.1: + dependencies: + import-fresh: 2.0.0 + is-directory: 0.3.1 + js-yaml: 3.14.2 + parse-json: 4.0.0 + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + create-jest@29.7.0(@types/node@25.1.0): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@25.1.0) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-random-string@2.0.0: {} + + csstype@3.2.3: {} + + dayjs@1.11.19: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + dedent@0.7.0: {} + + dedent@1.7.1: {} + + deep-extend@0.6.0: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-lazy-prop@2.0.0: {} + + del@6.1.1: + dependencies: + globby: 11.1.0 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 2.2.0 + is-path-inside: 3.0.3 + p-map: 4.0.0 + rimraf: 3.0.2 + slash: 3.0.0 + + denodeify@1.2.1: {} + + depd@2.0.0: {} + + deprecated-react-native-prop-types@5.0.0: + dependencies: + '@react-native/normalize-colors': 0.73.2 + invariant: 2.2.4 + prop-types: 15.8.1 + + destroy@1.2.0: {} + + detect-libc@2.1.2: {} + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.4.7 + + dotenv@16.4.7: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.279: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-editor@0.4.2: {} + + envinfo@7.21.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + errorhandler@1.5.2: + dependencies: + accepts: 1.3.8 + escape-html: 1.0.3 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + esprima@4.0.1: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + ethereum-cryptography@3.2.0: + dependencies: + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.0 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + + event-target-shim@5.0.1: {} + + eventemitter3@5.0.1: {} + + exec-async@2.2.0: {} + + execa@4.1.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + expo-application@6.1.5(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)): + dependencies: + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + + expo-asset@12.0.12(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1): + dependencies: + '@expo/image-utils': 0.8.8 + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.13(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + transitivePeerDependencies: + - supports-color + + expo-constants@18.0.13(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)): + dependencies: + '@expo/config': 12.0.13 + '@expo/env': 2.0.8 + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + transitivePeerDependencies: + - supports-color + + expo-device@7.1.4(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)): + dependencies: + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + ua-parser-js: 0.7.41 + + expo-file-system@19.0.21(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)): + dependencies: + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + expo-font@14.0.11(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1): + dependencies: + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + fontfaceobserver: 2.3.0 + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + expo-keep-awake@15.0.8(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react@18.3.1): + dependencies: + expo: 54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + react: 18.3.1 + + expo-modules-autolinking@3.0.24: + dependencies: + '@expo/spawn-async': 1.7.2 + chalk: 4.1.2 + commander: 7.2.0 + require-from-string: 2.0.2 + resolve-from: 5.0.0 + + expo-modules-core@3.0.29(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1): + dependencies: + invariant: 2.2.4 + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + expo-server@1.0.5: {} + + expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.6 + '@expo/cli': 54.0.23(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + '@expo/config': 12.0.13 + '@expo/config-plugins': 54.0.4 + '@expo/devtools': 0.1.8(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + '@expo/fingerprint': 0.15.4 + '@expo/metro': 54.2.0 + '@expo/metro-config': 54.0.14(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1)) + '@expo/vector-icons': 15.0.3(expo-font@14.0.11(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + '@ungap/structured-clone': 1.3.0 + babel-preset-expo: 54.0.10(@babel/core@7.28.6)(@babel/runtime@7.28.6)(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-refresh@0.14.2) + expo-asset: 12.0.12(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.13(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + expo-file-system: 19.0.21(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + expo-font: 14.0.11(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + expo-keep-awake: 15.0.8(expo@54.0.33(@babel/core@7.28.6)(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1))(react@18.3.1) + expo-modules-autolinking: 3.0.24 + expo-modules-core: 3.0.29(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1))(react@18.3.1) + pretty-format: 29.7.0 + react: 18.3.1 + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + react-refresh: 0.14.2 + whatwg-url-without-unicode: 8.0.0-3 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - expo-router + - graphql + - supports-color + - utf-8-validate + + exponential-backoff@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-xml-parser@4.5.3: + dependencies: + strnum: 1.1.2 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flow-enums-runtime@0.0.6: {} + + flow-parser@0.206.0: {} + + fontfaceobserver@2.3.0: {} + + freeport-async@2.0.0: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-package-type@0.1.0: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-stream@6.0.1: {} + + getenv@2.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.1: + dependencies: + minimatch: 10.1.2 + minipass: 7.1.2 + path-scurry: 2.0.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + global-dirs@0.1.1: + dependencies: + ini: 1.3.8 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.15.0: {} + + hermes-estree@0.23.1: {} + + hermes-estree@0.29.1: {} + + hermes-estree@0.32.0: {} + + hermes-parser@0.15.0: + dependencies: + hermes-estree: 0.15.0 + + hermes-parser@0.23.1: + dependencies: + hermes-estree: 0.23.1 + + hermes-parser@0.29.1: + dependencies: + hermes-estree: 0.29.1 + + hermes-parser@0.32.0: + dependencies: + hermes-estree: 0.32.0 + + hermes-profile-transformer@0.0.6: + dependencies: + source-map: 0.7.6 + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + html-escaper@2.0.2: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@1.1.1: {} + + human-signals@2.1.0: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + image-size@1.2.1: + dependencies: + queue: 6.0.2 + + import-fresh@2.0.0: + dependencies: + caller-path: 2.0.0 + resolve-from: 3.0.0 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + + is-arrayish@0.2.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-directory@0.3.1: {} + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@2.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-git-dirty@2.0.2: + dependencies: + execa: 4.1.0 + is-git-repository: 2.0.0 + + is-git-repository@2.0.0: + dependencies: + execa: 4.1.0 + is-absolute: 1.0.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-path-cwd@2.2.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@2.1.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + + is-stream@2.0.1: {} + + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + + is-unicode-supported@0.1.0: {} + + is-windows@1.0.2: {} + + is-wsl@1.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + isows@1.0.7(ws@8.18.3): + dependencies: + ws: 8.18.3 + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.1 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@25.1.0): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@25.1.0) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@25.1.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@25.1.0): + dependencies: + '@babel/core': 7.28.6 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.6) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 25.1.0 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 25.1.0 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.28.6 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.11 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) + '@babel/types': 7.28.6 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 25.1.0 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@25.1.0): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@25.1.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jimp-compact@0.16.1: {} + + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsc-android@250231.0.0: {} + + jsc-safe-url@0.2.4: {} + + jscodeshift@0.14.0(@babel/preset-env@7.29.0(@babel/core@7.28.6)): + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/preset-env': 7.29.0(@babel/core@7.28.6) + '@babel/preset-flow': 7.27.1(@babel/core@7.28.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) + '@babel/register': 7.28.6(@babel/core@7.28.6) + babel-core: 7.0.0-bridge.0(@babel/core@7.28.6) + chalk: 4.1.2 + flow-parser: 0.206.0 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + neo-async: 2.6.2 + node-dir: 0.1.17 + recast: 0.21.5 + temp: 0.8.4 + write-file-atomic: 2.4.3 + transitivePeerDependencies: + - supports-color + + jsesc@3.1.0: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + lan-network@0.1.7: {} + + leven@3.1.0: {} + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + lightningcss-android-arm64@1.31.1: + optional: true + + lightningcss-darwin-arm64@1.31.1: + optional: true + + lightningcss-darwin-x64@1.31.1: + optional: true + + lightningcss-freebsd-x64@1.31.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.31.1: + optional: true + + lightningcss-linux-arm64-gnu@1.31.1: + optional: true + + lightningcss-linux-arm64-musl@1.31.1: + optional: true + + lightningcss-linux-x64-gnu@1.31.1: + optional: true + + lightningcss-linux-x64-musl@1.31.1: + optional: true + + lightningcss-win32-arm64-msvc@1.31.1: + optional: true + + lightningcss-win32-x64-msvc@1.31.1: + optional: true + + lightningcss@1.31.1: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.31.1 + lightningcss-darwin-arm64: 1.31.1 + lightningcss-darwin-x64: 1.31.1 + lightningcss-freebsd-x64: 1.31.1 + lightningcss-linux-arm-gnueabihf: 1.31.1 + lightningcss-linux-arm64-gnu: 1.31.1 + lightningcss-linux-arm64-musl: 1.31.1 + lightningcss-linux-x64-gnu: 1.31.1 + lightningcss-linux-x64-musl: 1.31.1 + lightningcss-win32-arm64-msvc: 1.31.1 + lightningcss-win32-x64-msvc: 1.31.1 + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.throttle@4.1.1: {} + + log-symbols@2.2.0: + dependencies: + chalk: 2.4.2 + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + logkitty@0.7.1: + dependencies: + ansi-fragments: 0.2.1 + dayjs: 1.11.19 + yargs: 15.4.1 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@11.2.5: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + marky@1.3.0: {} + + memoize-one@5.2.1: {} + + merge-options@3.0.4: + dependencies: + is-plain-obj: 2.1.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + metro-babel-transformer@0.80.12: + dependencies: + '@babel/core': 7.28.6 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.23.1 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-babel-transformer@0.83.3: + dependencies: + '@babel/core': 7.28.6 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.32.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.80.12: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache-key@0.83.3: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.80.12: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + metro-core: 0.80.12 + + metro-cache@0.83.3: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.83.3 + transitivePeerDependencies: + - supports-color + + metro-config@0.80.12: + dependencies: + connect: 3.7.0 + cosmiconfig: 5.2.1 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.80.12 + metro-cache: 0.80.12 + metro-core: 0.80.12 + metro-runtime: 0.80.12 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-config@0.83.3: + dependencies: + connect: 3.7.0 + 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.8.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.80.12: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.80.12 + + metro-core@0.83.3: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.83.3 + + metro-file-map@0.80.12: + dependencies: + anymatch: 3.1.3 + debug: 2.6.9 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + node-abort-controller: 3.1.1 + nullthrows: 1.1.1 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - supports-color + + metro-file-map@0.83.3: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-minify-terser@0.80.12: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.46.0 + + metro-minify-terser@0.83.3: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.46.0 + + metro-resolver@0.80.12: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-resolver@0.83.3: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.80.12: + dependencies: + '@babel/runtime': 7.28.6 + flow-enums-runtime: 0.0.6 + + metro-runtime@0.83.3: + dependencies: + '@babel/runtime': 7.28.6 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.80.12: + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.80.12 + nullthrows: 1.1.1 + ob1: 0.80.12 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-source-map@0.83.3: + dependencies: + '@babel/traverse': 7.28.6 + '@babel/traverse--for-generate-function-map': '@babel/traverse@7.28.6' + '@babel/types': 7.28.6 + 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.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.80.12: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.80.12 + nullthrows: 1.1.1 + source-map: 0.5.7 + through2: 2.0.5 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + 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.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.80.12: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.83.3: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-worker@0.80.12: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + flow-enums-runtime: 0.0.6 + metro: 0.80.12 + metro-babel-transformer: 0.80.12 + metro-cache: 0.80.12 + metro-cache-key: 0.80.12 + metro-minify-terser: 0.80.12 + metro-source-map: 0.80.12 + metro-transform-plugins: 0.80.12 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-transform-worker@0.83.3: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + 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 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.80.12: + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + accepts: 1.3.8 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 2.6.9 + denodeify: 1.2.1 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.23.1 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.80.12 + metro-cache: 0.80.12 + metro-cache-key: 0.80.12 + metro-config: 0.80.12 + metro-core: 0.80.12 + metro-file-map: 0.80.12 + metro-resolver: 0.80.12 + metro-runtime: 0.80.12 + metro-source-map: 0.80.12 + metro-symbolicate: 0.80.12 + metro-transform-plugins: 0.80.12 + metro-transform-worker: 0.80.12 + mime-types: 2.1.35 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + strip-ansi: 6.0.1 + throat: 5.0.0 + ws: 7.5.10 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.83.3: + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + accepts: 1.3.8 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.32.0 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + 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.35 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.10 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mime@2.6.0: {} + + mimic-fn@1.2.0: {} + + mimic-fn@2.1.0: {} + + minimatch@10.1.2: + dependencies: + '@isaacs/brace-expansion': 5.0.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + mipd@0.0.7(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + neo-async@2.6.2: {} + + nested-error-stacks@2.0.1: {} + + nocache@3.0.4: {} + + node-abort-controller@3.1.1: {} + + node-dir@0.1.17: + dependencies: + minimatch: 3.1.2 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-forge@1.3.3: {} + + node-int64@0.4.0: {} + + node-releases@2.0.27: {} + + node-stream-zip@1.15.0: {} + + normalize-path@3.0.0: {} + + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.7.3 + validate-npm-package-name: 5.0.1 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nullthrows@1.1.1: {} + + ob1@0.80.12: + dependencies: + flow-enums-runtime: 0.0.6 + + ob1@0.83.3: + dependencies: + flow-enums-runtime: 0.0.6 + + object-assign@4.1.1: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@2.0.1: + dependencies: + mimic-fn: 1.2.0 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@6.4.0: + dependencies: + is-wsl: 1.1.0 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + ora@3.4.0: + dependencies: + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-spinners: 2.9.2 + log-symbols: 2.2.0 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + ox@0.11.3(typescript@5.9.3): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - zod + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.28.6 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-png@2.1.0: + dependencies: + pngjs: 3.4.0 + + parseurl@1.3.3: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.2 + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@3.0.1: {} + + picomatch@4.0.3: {} + + pify@4.0.1: {} + + pirates@4.0.7: {} + + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + pngjs@3.4.0: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-bytes@5.6.0: {} + + pretty-format@26.6.2: + dependencies: + '@jest/types': 26.6.2 + ansi-regex: 5.0.1 + ansi-styles: 4.3.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + proc-log@4.2.0: {} + + process-nextick-args@2.0.1: {} + + progress@2.0.3: {} + + promise@8.3.0: + dependencies: + asap: 2.0.6 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qrcode-terminal@0.11.0: {} + + queue-microtask@1.2.3: {} + + queue@6.0.2: + dependencies: + inherits: 2.0.4 + + range-parser@1.2.1: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-devtools-core@4.28.5: + dependencies: + shell-quote: 1.8.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-native-builder-bob@0.23.2: + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.6) + '@babel/preset-env': 7.29.0(@babel/core@7.28.6) + '@babel/preset-flow': 7.27.1(@babel/core@7.28.6) + '@babel/preset-react': 7.28.5(@babel/core@7.28.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) + browserslist: 4.28.1 + cosmiconfig: 7.1.0 + cross-spawn: 7.0.6 + dedent: 0.7.0 + del: 6.1.1 + fs-extra: 10.1.0 + glob: 8.1.0 + is-git-dirty: 2.0.2 + json5: 2.2.3 + kleur: 4.1.5 + prompts: 2.4.2 + which: 2.0.2 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + + react-native-device-info@14.1.1(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)): + dependencies: + react-native: 0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1) + + react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native-community/cli': 12.3.7 + '@react-native-community/cli-platform-android': 12.3.7 + '@react-native-community/cli-platform-ios': 12.3.7 + '@react-native/assets-registry': 0.73.1 + '@react-native/codegen': 0.73.3(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + '@react-native/community-cli-plugin': 0.73.18(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6)) + '@react-native/gradle-plugin': 0.73.5 + '@react-native/js-polyfills': 0.73.1 + '@react-native/normalize-colors': 0.73.2 + '@react-native/virtualized-lists': 0.73.4(react-native@0.73.11(@babel/core@7.28.6)(@babel/preset-env@7.29.0(@babel/core@7.28.6))(react@18.3.1)) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + base64-js: 1.5.1 + chalk: 4.1.2 + deprecated-react-native-prop-types: 5.0.0 + event-target-shim: 5.0.1 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + jsc-android: 250231.0.0 + memoize-one: 5.2.1 + metro-runtime: 0.80.12 + metro-source-map: 0.80.12 + mkdirp: 0.5.6 + nullthrows: 1.1.1 + pretty-format: 26.6.2 + promise: 8.3.0 + react: 18.3.1 + react-devtools-core: 4.28.5 + react-refresh: 0.14.2 + react-shallow-renderer: 16.15.0(react@18.3.1) + regenerator-runtime: 0.13.11 + scheduler: 0.24.0-canary-efb381bbf-20230505 + stacktrace-parser: 0.1.11 + whatwg-fetch: 3.6.20 + ws: 6.2.3 + yargs: 17.7.2 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + + react-refresh@0.14.2: {} + + react-shallow-renderer@16.15.0(react@18.3.1): + dependencies: + object-assign: 4.1.1 + react: 18.3.1 + react-is: 18.3.1 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readline@1.3.0: {} + + recast@0.21.5: + dependencies: + ast-types: 0.15.2 + esprima: 4.0.1 + source-map: 0.6.1 + tslib: 2.8.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.13.11: {} + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-main-filename@2.0.0: {} + + requireg@0.2.2: + dependencies: + nested-error-stacks: 2.0.1 + rc: 1.2.8 + resolve: 1.7.1 + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@3.0.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-global@1.0.0: + dependencies: + global-dirs: 0.1.1 + + resolve-workspace-root@2.0.1: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@1.7.1: + dependencies: + path-parse: 1.0.7 + + restore-cursor@2.0.0: + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.7 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@2.6.3: + dependencies: + glob: 7.2.3 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + sax@1.4.4: {} + + scheduler@0.24.0-canary-efb381bbf-20230505: + dependencies: + loose-envify: 1.4.0 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + set-blocking@2.0.0: {} + + setprototypeof@1.2.0: {} + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + signal-exit@3.0.7: {} + + simple-plist@1.3.1: + dependencies: + bplist-creator: 0.1.0 + bplist-parser: 0.3.1 + plist: 3.1.0 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@2.1.0: + dependencies: + ansi-styles: 3.2.1 + astral-regex: 1.0.0 + is-fullwidth-code-point: 2.0.0 + + slugify@1.6.6: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackframe@1.3.4: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + stream-buffers@2.2.0: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + strnum@1.1.2: {} + + structured-headers@0.4.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + sudo-prompt@9.2.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@2.3.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tar@7.5.7: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + temp-dir@2.0.0: {} + + temp@0.8.4: + dependencies: + rimraf: 2.6.3 + + terminal-link@2.1.1: + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + + terser@5.46.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + throat@5.0.0: {} + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@0.7.1: {} + + typescript@5.9.3: {} + + ua-parser-js@0.7.41: {} + + unc-path-regex@0.1.2: {} + + undici-types@7.16.0: {} + + undici@6.23.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unique-string@2.0.0: + dependencies: + crypto-random-string: 2.0.0 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.4.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@7.0.3: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + validate-npm-package-name@5.0.1: {} + + vary@1.1.2: {} + + viem@2.45.0(typescript@5.9.3): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3) + isows: 1.0.7(ws@8.18.3) + ox: 0.11.3(typescript@5.9.3) + ws: 8.18.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + + vlq@1.0.1: {} + + wagmi@3.4.1(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@18.3.1))(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(viem@2.45.0(typescript@5.9.3)): + dependencies: + '@tanstack/react-query': 5.90.20(react@18.3.1) + '@wagmi/connectors': 7.1.5(@wagmi/core@3.3.1(@tanstack/query-core@5.90.20)(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.45.0(typescript@5.9.3)))(typescript@5.9.3)(viem@2.45.0(typescript@5.9.3)) + '@wagmi/core': 3.3.1(@tanstack/query-core@5.90.20)(@types/react@18.3.27)(ox@0.11.3(typescript@5.9.3))(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(viem@2.45.0(typescript@5.9.3)) + react: 18.3.1 + use-sync-external-store: 1.4.0(react@18.3.1) + viem: 2.45.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@base-org/account' + - '@coinbase/wallet-sdk' + - '@gemini-wallet/core' + - '@metamask/sdk' + - '@safe-global/safe-apps-provider' + - '@safe-global/safe-apps-sdk' + - '@tanstack/query-core' + - '@types/react' + - '@walletconnect/ethereum-provider' + - immer + - ox + - porto + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webidl-conversions@5.0.0: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url-without-unicode@8.0.0-3: + dependencies: + buffer: 5.7.1 + punycode: 2.3.1 + webidl-conversions: 5.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-module@2.0.1: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wonka@6.3.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@2.4.3: + dependencies: + graceful-fs: 4.2.11 + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@6.2.3: + dependencies: + async-limiter: 1.0.1 + + ws@7.5.10: {} + + ws@8.18.3: {} + + xcode@3.0.1: + dependencies: + simple-plist: 1.3.1 + uuid: 7.0.3 + + xml2js@0.6.0: + dependencies: + sax: 1.4.4 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xmlbuilder@15.1.1: {} + + xtend@4.0.2: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yaml@1.10.2: {} + + yaml@2.8.2: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@21.1.1: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zustand@5.0.0(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.27 + react: 18.3.1 + use-sync-external-store: 1.4.0(react@18.3.1) diff --git a/src/FormoAnalytics.ts b/src/FormoAnalytics.ts new file mode 100644 index 0000000..2b89649 --- /dev/null +++ b/src/FormoAnalytics.ts @@ -0,0 +1,653 @@ +/** + * FormoAnalytics for React Native + * + * Main SDK class for tracking wallet events and user analytics in mobile dApps + */ + +import { + EVENTS_API_HOST, + EventType, + LOCAL_ANONYMOUS_ID_KEY, + SESSION_USER_ID_KEY, + CONSENT_OPT_OUT_KEY, + TEventType, +} from "./constants"; +import { initStorageManager, storage, AsyncStorageInterface } from "./lib/storage"; +import { EventManager, EventQueue, IEventManager } from "./lib/event"; +import { logger, Logger } from "./lib/logger"; +import { + setConsentFlag, + getConsentFlag, + removeConsentFlag, +} from "./lib/consent"; +import { FormoAnalyticsSession } from "./lib/session"; +import { WagmiEventHandler } from "./lib/wagmi"; +import { + Address, + ChainID, + Config, + IFormoAnalytics, + IFormoEventContext, + IFormoEventProperties, + Options, + SignatureStatus, + TrackingOptions, + TransactionStatus, +} from "./types"; +import { toChecksumAddress, getValidAddress } from "./utils"; + +export class FormoAnalytics implements IFormoAnalytics { + private session: FormoAnalyticsSession; + private eventManager: IEventManager; + private eventQueue: EventQueue; + private wagmiHandler?: WagmiEventHandler; + + config: Config; + currentChainId?: ChainID; + currentAddress?: Address; + currentUserId?: string = ""; + + private constructor( + public readonly writeKey: string, + public options: Options = {} + ) { + this.config = { writeKey }; + + this.session = new FormoAnalyticsSession(); + this.currentUserId = + (storage().get(SESSION_USER_ID_KEY) as string) || undefined; + + // Bind methods + this.identify = this.identify.bind(this); + this.connect = this.connect.bind(this); + this.disconnect = this.disconnect.bind(this); + this.chain = this.chain.bind(this); + this.signature = this.signature.bind(this); + this.transaction = this.transaction.bind(this); + this.detect = this.detect.bind(this); + this.track = this.track.bind(this); + this.screen = this.screen.bind(this); + this.isAutocaptureEnabled = this.isAutocaptureEnabled.bind(this); + + // Initialize logger + Logger.init({ + enabled: options.logger?.enabled || false, + enabledLevels: options.logger?.levels || [], + }); + + // Initialize event queue + this.eventQueue = new EventQueue(this.config.writeKey, { + apiHost: options.apiHost || EVENTS_API_HOST, + flushAt: options.flushAt, + retryCount: options.retryCount, + maxQueueSize: options.maxQueueSize, + flushInterval: options.flushInterval, + }); + + // Initialize event manager + this.eventManager = new EventManager(this.eventQueue, options); + + // Check consent status + if (this.hasOptedOutTracking()) { + logger.info("User has previously opted out of tracking"); + } + + // Initialize Wagmi handler if provided and config is valid + if (options.wagmi?.config) { + logger.info("FormoAnalytics: Initializing in Wagmi mode"); + this.wagmiHandler = new WagmiEventHandler( + this, + options.wagmi.config, + options.wagmi.queryClient + ); + } else if (options.wagmi) { + logger.warn("FormoAnalytics: wagmi option provided but config is missing"); + } + } + + /** + * Initialize the SDK + * @param writeKey - Your Formo write key + * @param options - Configuration options + * @param asyncStorage - AsyncStorage instance from @react-native-async-storage/async-storage + */ + static async init( + writeKey: string, + options?: Options, + asyncStorage?: AsyncStorageInterface + ): Promise { + const storageManager = initStorageManager(writeKey); + + // Initialize storage with AsyncStorage if provided + if (asyncStorage) { + await storageManager.initialize(asyncStorage); + } + + const analytics = new FormoAnalytics(writeKey, options); + + // Call ready callback + if (options?.ready) { + options.ready(analytics); + } + + return analytics; + } + + /** + * Track a screen view (mobile equivalent of page view) + */ + public async screen( + name: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + // Note: shouldTrack() is called in trackEvent() - no need to check here + await this.trackEvent( + EventType.SCREEN, + { name }, + properties, + context, + callback + ); + } + + /** + * Set traffic source from deep link URL + * Parses UTM parameters and referrer information from URL + * This is automatically persisted for the session + * + * @param url - Deep link URL (e.g., "myapp://product?utm_source=facebook&ref=friend123") + * + * @example + * ```tsx + * import { Linking } from 'react-native'; + * + * // Listen for deep links + * Linking.addEventListener('url', (event) => { + * formo.setTrafficSourceFromUrl(event.url); + * }); + * + * // Or get initial URL + * Linking.getInitialURL().then((url) => { + * if (url) formo.setTrafficSourceFromUrl(url); + * }); + * ``` + */ + public setTrafficSourceFromUrl(url: string): void { + const { parseTrafficSource, storeTrafficSource } = require("./utils/trafficSource"); + const trafficSource = parseTrafficSource(url); + storeTrafficSource(trafficSource); + logger.debug("Traffic source set from URL:", trafficSource); + } + + /** + * Reset the current user session + */ + public reset(): void { + this.currentUserId = undefined; + storage().remove(LOCAL_ANONYMOUS_ID_KEY); + storage().remove(SESSION_USER_ID_KEY); + this.session.clear(); + } + + /** + * Clean up resources + */ + public async cleanup(): Promise { + logger.info("FormoAnalytics: Cleaning up resources"); + + if (this.wagmiHandler) { + this.wagmiHandler.cleanup(); + this.wagmiHandler = undefined; + } + + if (this.eventQueue) { + await this.eventQueue.cleanup(); + } + + logger.info("FormoAnalytics: Cleanup complete"); + } + + /** + * Track wallet connect event + */ + async connect( + { chainId, address }: { chainId: ChainID; address: Address }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + if (chainId === null || chainId === undefined || Number(chainId) === 0) { + logger.warn("Connect: Chain ID cannot be null, undefined, or 0"); + return; + } + if (!address) { + logger.warn("Connect: Address cannot be empty"); + return; + } + + const checksummedAddress = this.validateAndChecksumAddress(address); + if (!checksummedAddress) { + logger.warn(`Connect: Invalid address provided ("${address}")`); + return; + } + + // Track event before updating state so connect events TO excluded chains are tracked + await this.trackEvent( + EventType.CONNECT, + { chainId, address: checksummedAddress }, + properties, + context, + callback + ); + + this.currentChainId = chainId; + this.currentAddress = checksummedAddress; + } + + /** + * Track wallet disconnect event + */ + async disconnect( + params?: { chainId?: ChainID; address?: Address }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + const chainId = params?.chainId || this.currentChainId; + const address = params?.address || this.currentAddress; + + logger.info("Disconnect: Emitting disconnect event with:", { + chainId, + address, + }); + + await this.trackEvent( + EventType.DISCONNECT, + { + ...(chainId && { chainId }), + ...(address && { address }), + }, + properties, + context, + callback + ); + + this.currentAddress = undefined; + this.currentChainId = undefined; + } + + /** + * Track chain change event + */ + async chain( + { chainId, address }: { chainId: ChainID; address?: Address }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + if (!chainId || Number(chainId) === 0) { + logger.warn("FormoAnalytics::chain: chainId cannot be empty or 0"); + return; + } + if (isNaN(Number(chainId))) { + logger.warn("FormoAnalytics::chain: chainId must be a valid number"); + return; + } + if (!address && !this.currentAddress) { + logger.warn("FormoAnalytics::chain: address was empty and no previous address recorded"); + return; + } + + // Track event before updating currentChainId so shouldTrack uses the previous chain + // This ensures chain change events TO excluded chains are still tracked + await this.trackEvent( + EventType.CHAIN, + { chainId, address: address || this.currentAddress }, + properties, + context, + callback + ); + + this.currentChainId = chainId; + } + + /** + * Track signature event + */ + async signature( + { + status, + chainId, + address, + message, + signatureHash, + }: { + status: SignatureStatus; + chainId: ChainID; + address: Address; + message: string; + signatureHash?: string; + }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + if (chainId === null || chainId === undefined || Number(chainId) === 0) { + logger.warn("Signature: Chain ID cannot be null, undefined, or 0"); + return; + } + if (!address) { + logger.warn("Signature: Address cannot be empty"); + return; + } + await this.trackEvent( + EventType.SIGNATURE, + { + status, + chainId, + address, + message, + ...(signatureHash && { signatureHash }), + }, + properties, + context, + callback + ); + } + + /** + * Track transaction event + */ + async transaction( + { + status, + chainId, + address, + data, + to, + value, + transactionHash, + }: { + status: TransactionStatus; + chainId: ChainID; + address: Address; + data?: string; + to?: string; + value?: string; + transactionHash?: string; + }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + if (chainId === null || chainId === undefined || Number(chainId) === 0) { + logger.warn("Transaction: Chain ID cannot be null, undefined, or 0"); + return; + } + if (!address) { + logger.warn("Transaction: Address cannot be empty"); + return; + } + await this.trackEvent( + EventType.TRANSACTION, + { + status, + chainId, + address, + data, + to, + value, + ...(transactionHash && { transactionHash }), + }, + properties, + context, + callback + ); + } + + /** + * Track identify event + */ + async identify( + params: { + address: Address; + providerName?: string; + userId?: string; + rdns?: string; + }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + try { + const { userId, address, providerName, rdns } = params; + logger.info("Identify", address, userId, providerName, rdns); + + let validAddress: Address | undefined = undefined; + if (address) { + validAddress = this.validateAndChecksumAddress(address); + if (!validAddress) { + logger.warn(`Identify: Invalid address provided ("${address}")`); + return; + } + this.currentAddress = validAddress; + } else { + this.currentAddress = undefined; + } + + if (userId) { + this.currentUserId = userId; + storage().set(SESSION_USER_ID_KEY, userId); + } + + // Check for duplicate identify + const isAlreadyIdentified = validAddress + ? this.session.isWalletIdentified(validAddress, rdns || "") + : false; + + if (isAlreadyIdentified) { + logger.info( + `Identify: Wallet ${providerName || "Unknown"} with address ${validAddress} already identified` + ); + return; + } + + // Mark as identified + if (validAddress) { + this.session.markWalletIdentified(validAddress, rdns || ""); + } + + await this.trackEvent( + EventType.IDENTIFY, + { address: validAddress, providerName, userId, rdns }, + properties, + context, + callback + ); + } catch (e) { + logger.log("identify error", e); + } + } + + /** + * Track detect wallet event + */ + async detect( + { providerName, rdns }: { providerName: string; rdns: string }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + if (this.session.isWalletDetected(rdns)) { + logger.warn(`Detect: Wallet ${providerName} already detected in this session`); + return; + } + + this.session.markWalletDetected(rdns); + await this.trackEvent( + EventType.DETECT, + { providerName, rdns }, + properties, + context, + callback + ); + } + + /** + * Track custom event + */ + async track( + event: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + await this.trackEvent( + EventType.TRACK, + { event }, + properties, + context, + callback + ); + } + + /** + * Opt out of tracking + */ + public optOutTracking(): void { + logger.info("Opting out of tracking"); + setConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY, "true"); + this.eventQueue.clear(); + this.reset(); + logger.info("Successfully opted out of tracking"); + } + + /** + * Opt back into tracking + */ + public optInTracking(): void { + logger.info("Opting back into tracking"); + removeConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY); + logger.info("Successfully opted back into tracking"); + } + + /** + * Check if user has opted out + */ + public hasOptedOutTracking(): boolean { + return getConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY) === "true"; + } + + /** + * Check if autocapture is enabled for event type + */ + public isAutocaptureEnabled( + eventType: "connect" | "disconnect" | "signature" | "transaction" | "chain" + ): boolean { + if (this.options.autocapture === undefined) { + return true; + } + + if (typeof this.options.autocapture === "boolean") { + return this.options.autocapture; + } + + if ( + this.options.autocapture !== null && + typeof this.options.autocapture === "object" + ) { + const eventConfig = this.options.autocapture[eventType]; + return eventConfig !== false; + } + + return true; + } + + /** + * Internal method to track events + * This is the single enforcement point for shouldTrack() - all public tracking + * methods (track, screen, connect, etc.) route through here + */ + private async trackEvent( + type: TEventType, + payload?: Record, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise { + try { + if (!this.shouldTrack()) { + logger.info(`Skipping ${type} event due to tracking configuration`); + return; + } + + await this.eventManager.addEvent( + { + type, + ...payload, + properties, + context, + callback, + } as any, + this.currentAddress, + this.currentUserId + ); + } catch (error) { + logger.error("Error tracking event:", error); + } + } + + /** + * Check if tracking should be enabled + */ + private shouldTrack(): boolean { + // Check consent + if (this.hasOptedOutTracking()) { + return false; + } + + // Check tracking option + if (typeof this.options.tracking === "boolean") { + return this.options.tracking; + } + + // Handle object configuration + if ( + this.options.tracking !== null && + typeof this.options.tracking === "object" && + !Array.isArray(this.options.tracking) + ) { + const { excludeChains = [] } = this.options.tracking as TrackingOptions; + + if ( + excludeChains.length > 0 && + this.currentChainId && + excludeChains.includes(this.currentChainId) + ) { + return false; + } + + return true; + } + + // Default: track + return true; + } + + /** + * Validate and checksum address + */ + private validateAndChecksumAddress(address: string): Address | undefined { + const validAddress = getValidAddress(address); + return validAddress ? toChecksumAddress(validAddress) : undefined; + } + + /** + * Flush pending events + */ + public async flush(): Promise { + await this.eventQueue.flush(); + } +} diff --git a/src/FormoAnalyticsProvider.tsx b/src/FormoAnalyticsProvider.tsx new file mode 100644 index 0000000..93aa443 --- /dev/null +++ b/src/FormoAnalyticsProvider.tsx @@ -0,0 +1,314 @@ +import React, { + createContext, + useContext, + useEffect, + useMemo, + useRef, + useState, + FC, +} from "react"; +import { FormoAnalytics } from "./FormoAnalytics"; +import { initStorageManager, AsyncStorageInterface } from "./lib/storage"; +import { logger } from "./lib/logger"; +import { FormoAnalyticsProviderProps, IFormoAnalytics } from "./types"; + +// Default context with no-op methods +const defaultContext: IFormoAnalytics = { + chain: () => Promise.resolve(), + screen: () => Promise.resolve(), + reset: () => {}, + cleanup: () => Promise.resolve(), + detect: () => Promise.resolve(), + connect: () => Promise.resolve(), + disconnect: () => Promise.resolve(), + signature: () => Promise.resolve(), + transaction: () => Promise.resolve(), + identify: () => Promise.resolve(), + track: () => Promise.resolve(), + setTrafficSourceFromUrl: () => {}, + optOutTracking: () => {}, + optInTracking: () => {}, + hasOptedOutTracking: () => false, +}; + +export const FormoAnalyticsContext = + createContext(defaultContext); + +export interface FormoAnalyticsProviderPropsWithStorage + extends FormoAnalyticsProviderProps { + /** + * AsyncStorage instance from @react-native-async-storage/async-storage + * Required for persistent storage + */ + asyncStorage?: AsyncStorageInterface; + /** + * Callback when SDK is ready + * Note: Use useCallback to avoid re-initialization on every render + */ + onReady?: (sdk: IFormoAnalytics) => void; + /** + * Callback when SDK initialization fails + * Note: Use useCallback to avoid re-initialization on every render + */ + onError?: (error: Error) => void; +} + +/** + * Formo Analytics Provider for React Native + * + * Wraps your app to provide analytics context + * + * @example + * ```tsx + * import AsyncStorage from '@react-native-async-storage/async-storage'; + * import { FormoAnalyticsProvider } from '@formo/react-native-analytics'; + * + * function App() { + * return ( + * + * + * + * ); + * } + * ``` + */ +export const FormoAnalyticsProvider: FC = ( + props +) => { + const { writeKey, disabled = false, children } = props; + + if (!writeKey) { + logger.error("FormoAnalyticsProvider: No Write Key provided"); + return ( + + {children} + + ); + } + + if (disabled) { + logger.warn("FormoAnalytics is disabled"); + return ( + + {children} + + ); + } + + return ; +}; + +const InitializedAnalytics: FC = ({ + writeKey, + options, + asyncStorage, + onReady, + onError, + children, +}) => { + const [sdk, setSdk] = useState(defaultContext); + const sdkRef = useRef(defaultContext); + const storageInitKeyRef = useRef(null); + const cleanupPromiseRef = useRef>(Promise.resolve()); + const initializationIdRef = useRef(0); + + // Only initialize storage manager when writeKey changes, not on every render + if (storageInitKeyRef.current !== writeKey) { + initStorageManager(writeKey); + storageInitKeyRef.current = writeKey; + } + + // Store callbacks and options in refs to avoid re-initialization when references change + // This fixes the issue where inline arrow functions cause repeated SDK init + const onReadyRef = useRef(onReady); + const onErrorRef = useRef(onError); + const optionsRef = useRef(options); + + // Update refs when values change (without triggering effect) + useEffect(() => { + onReadyRef.current = onReady; + }, [onReady]); + + useEffect(() => { + onErrorRef.current = onError; + }, [onError]); + + useEffect(() => { + optionsRef.current = options; + }, [options]); + + // Extract individual option values to avoid reference equality issues with options object + const tracking = options?.tracking; + const autocapture = options?.autocapture; + const apiHost = options?.apiHost; + const flushAt = options?.flushAt; + const flushInterval = options?.flushInterval; + const retryCount = options?.retryCount; + const maxQueueSize = options?.maxQueueSize; + const loggerOption = options?.logger; + const app = options?.app; + const hasReady = !!options?.ready; + const wagmiConfig = options?.wagmi?.config; + const wagmiQueryClient = options?.wagmi?.queryClient; + + // Create stable key from serializable options + const optionsKey = useMemo(() => { + const serializableOptions = { + tracking, + autocapture, + apiHost, + flushAt, + flushInterval, + retryCount, + maxQueueSize, + logger: loggerOption, + app, + hasReady, + }; + + try { + return JSON.stringify(serializableOptions); + } catch (error) { + logger.warn("Failed to serialize options, using timestamp", error); + return Date.now().toString(); + } + }, [tracking, autocapture, apiHost, flushAt, flushInterval, retryCount, maxQueueSize, loggerOption, app, hasReady]); + + useEffect(() => { + // Increment initialization ID to track which initialization is current + const currentInitId = ++initializationIdRef.current; + let isCleanedUp = false; + + const initialize = async () => { + // Wait for any pending cleanup to complete before re-initializing + // This prevents race conditions between cleanup and init + await cleanupPromiseRef.current; + + // Check if this initialization is still current after awaiting cleanup + if (currentInitId !== initializationIdRef.current || isCleanedUp) { + logger.debug("Skipping stale initialization"); + return; + } + + // Clean up existing SDK and await flush completion + if (sdkRef.current && sdkRef.current !== defaultContext) { + logger.log("Cleaning up existing FormoAnalytics SDK instance"); + const cleanup = sdkRef.current.cleanup(); + cleanupPromiseRef.current = cleanup; + await cleanup; + sdkRef.current = defaultContext; + setSdk(defaultContext); + } + + // Check again after cleanup + if (currentInitId !== initializationIdRef.current || isCleanedUp) { + logger.debug("Skipping stale initialization after cleanup"); + return; + } + + try { + // Use optionsRef.current to ensure we have the latest options + const sdkInstance = await FormoAnalytics.init( + writeKey, + optionsRef.current, + asyncStorage + ); + + // Verify this initialization is still current + if (currentInitId !== initializationIdRef.current || isCleanedUp) { + logger.log("Initialization superseded, cleaning up new instance"); + await sdkInstance.cleanup(); + return; + } + + setSdk(sdkInstance); + sdkRef.current = sdkInstance; + logger.log("Successfully initialized FormoAnalytics SDK"); + + // Call onReady callback using the ref (stable reference) + onReadyRef.current?.(sdkInstance); + } catch (error) { + if (currentInitId === initializationIdRef.current && !isCleanedUp) { + logger.error("Failed to initialize FormoAnalytics SDK", error); + // Call onError callback using the ref (stable reference) + onErrorRef.current?.(error instanceof Error ? error : new Error(String(error))); + } + } + }; + + initialize(); + + return () => { + isCleanedUp = true; + + if (sdkRef.current && sdkRef.current !== defaultContext) { + logger.log("Cleaning up FormoAnalytics SDK instance"); + // Store cleanup promise so next initialization can await it + const cleanup = sdkRef.current.cleanup().catch((error) => { + logger.error("Error during SDK cleanup:", error); + }); + cleanupPromiseRef.current = cleanup; + sdkRef.current = defaultContext; + } + }; + // Note: onReady and onError are NOT in the dependency array + // They are accessed via refs to prevent re-initialization + // wagmiConfig and wagmiQueryClient are tracked separately since they're not serializable + }, [writeKey, optionsKey, asyncStorage, wagmiConfig, wagmiQueryClient]); + + return ( + + {children} + + ); +}; + +/** + * Hook to access Formo Analytics + * + * @example + * ```tsx + * import { useFormo } from '@formo/react-native-analytics'; + * + * function MyScreen() { + * const formo = useFormo(); + * + * useEffect(() => { + * formo.screen('Home'); + * }, []); + * + * const handleButtonPress = () => { + * formo.track('Button Pressed', { buttonName: 'signup' }); + * }; + * + * return ; + * } + * ``` + */ +// Track if the useFormo warning has been logged to avoid console spam +let hasLoggedUseFormoWarning = false; + +export const useFormo = (): IFormoAnalytics => { + const context = useContext(FormoAnalyticsContext); + + // Check if SDK has been initialized (context will be defaultContext before init completes) + // Only log the warning once to avoid flooding the console during async initialization + if (context === defaultContext && !hasLoggedUseFormoWarning) { + hasLoggedUseFormoWarning = true; + logger.warn( + "useFormo called before SDK initialization complete. " + + "Ensure FormoAnalyticsProvider is mounted and writeKey is provided." + ); + } + + // Reset the warning flag when SDK is initialized so it can warn again after a reset + if (context !== defaultContext) { + hasLoggedUseFormoWarning = false; + } + + return context; +}; diff --git a/src/__tests__/FormoAnalytics.test.ts b/src/__tests__/FormoAnalytics.test.ts new file mode 100644 index 0000000..41f4090 --- /dev/null +++ b/src/__tests__/FormoAnalytics.test.ts @@ -0,0 +1,430 @@ +import { SignatureStatus, TransactionStatus } from '../types'; + +// Mock instances that persist across tests +const mockStorageInstance = { + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + isAvailable: jest.fn(), +}; + +const mockStorageManager = { + initialize: jest.fn(), + getPrimaryStorage: jest.fn(), + getStorage: jest.fn(), +}; + +const mockEventManager = { + addEvent: jest.fn(), +}; + +const mockEventQueue = { + flush: jest.fn(), + clear: jest.fn(), + cleanup: jest.fn(), +}; + +const mockSession = { + isWalletDetected: jest.fn(), + isWalletIdentified: jest.fn(), + markWalletDetected: jest.fn(), + markWalletIdentified: jest.fn(), + clear: jest.fn(), +}; + +// Mock dependencies +jest.mock('../lib/storage', () => ({ + __esModule: true, + initStorageManager: jest.fn(), + storage: jest.fn(), + getStorageManager: jest.fn(), +})); + +jest.mock('../lib/event', () => ({ + __esModule: true, + EventManager: jest.fn(), + EventQueue: jest.fn(), +})); + +jest.mock('../lib/session', () => ({ + __esModule: true, + FormoAnalyticsSession: jest.fn(), +})); + +jest.mock('../lib/consent', () => ({ + __esModule: true, + setConsentFlag: jest.fn(), + getConsentFlag: jest.fn(), + removeConsentFlag: jest.fn(), +})); + +jest.mock('../lib/logger', () => ({ + __esModule: true, + logger: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + log: jest.fn(), + }, + Logger: { + init: jest.fn(), + }, +})); + +// Import after mocking +import { FormoAnalytics } from '../FormoAnalytics'; +import { initStorageManager, storage } from '../lib/storage'; +import { EventManager, EventQueue } from '../lib/event'; +import { FormoAnalyticsSession } from '../lib/session'; +import { setConsentFlag, getConsentFlag, removeConsentFlag } from '../lib/consent'; + +// Helper to setup all mock implementations +const setupMocks = () => { + // Storage mocks + mockStorageInstance.get.mockReturnValue(null); + mockStorageInstance.set.mockReturnValue(undefined); + mockStorageInstance.remove.mockReturnValue(undefined); + mockStorageInstance.isAvailable.mockReturnValue(true); + + mockStorageManager.initialize.mockResolvedValue(undefined); + mockStorageManager.getPrimaryStorage.mockReturnValue(mockStorageInstance); + mockStorageManager.getStorage.mockReturnValue(mockStorageInstance); + + (initStorageManager as jest.Mock).mockReturnValue(mockStorageManager); + (storage as jest.Mock).mockReturnValue(mockStorageInstance); + + // Event mocks + mockEventManager.addEvent.mockResolvedValue(undefined); + mockEventQueue.flush.mockResolvedValue(undefined); + mockEventQueue.clear.mockReturnValue(undefined); + mockEventQueue.cleanup.mockResolvedValue(undefined); + + (EventManager as jest.Mock).mockImplementation(() => mockEventManager); + (EventQueue as jest.Mock).mockImplementation(() => mockEventQueue); + + // Session mocks + mockSession.isWalletDetected.mockReturnValue(false); + mockSession.isWalletIdentified.mockReturnValue(false); + mockSession.markWalletDetected.mockReturnValue(undefined); + mockSession.markWalletIdentified.mockReturnValue(undefined); + mockSession.clear.mockReturnValue(undefined); + + (FormoAnalyticsSession as jest.Mock).mockImplementation(() => mockSession); + + // Consent mocks + (getConsentFlag as jest.Mock).mockReturnValue(null); +}; + +describe('FormoAnalytics', () => { + let analytics: FormoAnalytics; + const writeKey = 'test-write-key'; + + beforeEach(async () => { + // Re-setup mock implementations after clearMocks + setupMocks(); + analytics = await FormoAnalytics.init(writeKey); + }); + + describe('init()', () => { + it('should initialize with writeKey', async () => { + const instance = await FormoAnalytics.init('my-key'); + expect(instance.config.writeKey).toBe('my-key'); + }); + + it('should call initStorageManager with writeKey', async () => { + await FormoAnalytics.init('my-key'); + expect(initStorageManager).toHaveBeenCalledWith('my-key'); + }); + + it('should call ready callback if provided', async () => { + const readyCallback = jest.fn(); + await FormoAnalytics.init('my-key', { ready: readyCallback }); + expect(readyCallback).toHaveBeenCalled(); + }); + + it('should initialize storage with asyncStorage if provided', async () => { + const mockAsyncStorage = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), + }; + + await FormoAnalytics.init('my-key', {}, mockAsyncStorage as any); + + expect(mockStorageManager.initialize).toHaveBeenCalledWith(mockAsyncStorage); + }); + }); + + describe('connect()', () => { + it('should not track if chainId is null', async () => { + await analytics.connect({ chainId: null as any, address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2' }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if chainId is 0', async () => { + await analytics.connect({ chainId: 0, address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2' }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if address is empty', async () => { + await analytics.connect({ chainId: 1, address: '' }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if address is invalid', async () => { + await analytics.connect({ chainId: 1, address: 'invalid-address' }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should track valid connect event', async () => { + await analytics.connect({ + chainId: 1, + address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2', + }); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + + it('should update currentChainId and currentAddress after connect', async () => { + await analytics.connect({ + chainId: 1, + address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2', + }); + + expect(analytics.currentChainId).toBe(1); + expect(analytics.currentAddress).toBeDefined(); + }); + }); + + describe('disconnect()', () => { + it('should track disconnect event', async () => { + await analytics.disconnect(); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + + it('should clear currentAddress and currentChainId after disconnect', async () => { + analytics.currentChainId = 1; + analytics.currentAddress = '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2'; + + await analytics.disconnect(); + + expect(analytics.currentChainId).toBeUndefined(); + expect(analytics.currentAddress).toBeUndefined(); + }); + }); + + describe('chain()', () => { + beforeEach(async () => { + // Set up a connected state + analytics.currentAddress = '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2'; + analytics.currentChainId = 1; + }); + + it('should not track if chainId is empty', async () => { + await analytics.chain({ chainId: 0 }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if chainId is invalid', async () => { + await analytics.chain({ chainId: NaN }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if no address is available', async () => { + analytics.currentAddress = undefined; + + await analytics.chain({ chainId: 137 }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should track valid chain change event', async () => { + await analytics.chain({ chainId: 137 }); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + + it('should update currentChainId after chain change', async () => { + await analytics.chain({ chainId: 137 }); + + expect(analytics.currentChainId).toBe(137); + }); + }); + + describe('signature()', () => { + it('should not track if chainId is invalid', async () => { + await analytics.signature({ + status: SignatureStatus.REQUESTED, + chainId: 0, + address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2', + message: 'test message', + }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if address is empty', async () => { + await analytics.signature({ + status: SignatureStatus.REQUESTED, + chainId: 1, + address: '', + message: 'test message', + }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should track valid signature event', async () => { + await analytics.signature({ + status: SignatureStatus.CONFIRMED, + chainId: 1, + address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2', + message: 'test message', + signatureHash: '0xabc123', + }); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + }); + + describe('transaction()', () => { + it('should not track if chainId is invalid', async () => { + await analytics.transaction({ + status: TransactionStatus.STARTED, + chainId: 0, + address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2', + }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should not track if address is empty', async () => { + await analytics.transaction({ + status: TransactionStatus.STARTED, + chainId: 1, + address: '', + }); + + expect(mockEventManager.addEvent).not.toHaveBeenCalled(); + }); + + it('should track valid transaction event', async () => { + await analytics.transaction({ + status: TransactionStatus.CONFIRMED, + chainId: 1, + address: '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2', + transactionHash: '0xdef456', + value: '1000000000000000000', + }); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + }); + + describe('track()', () => { + it('should track custom events', async () => { + await analytics.track('button_click', { button_id: 'submit' }); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + }); + + describe('screen()', () => { + it('should track screen views', async () => { + await analytics.screen('HomeScreen', { section: 'featured' }); + + expect(mockEventManager.addEvent).toHaveBeenCalled(); + }); + }); + + describe('opt-out tracking', () => { + it('should return false for hasOptedOutTracking by default', () => { + expect(analytics.hasOptedOutTracking()).toBe(false); + }); + + it('should set opt-out flag when optOutTracking is called', async () => { + analytics.optOutTracking(); + + expect(setConsentFlag).toHaveBeenCalled(); + }); + + it('should remove opt-out flag when optInTracking is called', () => { + analytics.optInTracking(); + + expect(removeConsentFlag).toHaveBeenCalled(); + }); + }); + + describe('isAutocaptureEnabled()', () => { + it('should return true by default', () => { + expect(analytics.isAutocaptureEnabled('connect')).toBe(true); + expect(analytics.isAutocaptureEnabled('disconnect')).toBe(true); + expect(analytics.isAutocaptureEnabled('signature')).toBe(true); + expect(analytics.isAutocaptureEnabled('transaction')).toBe(true); + expect(analytics.isAutocaptureEnabled('chain')).toBe(true); + }); + + it('should return false when autocapture is disabled globally', async () => { + const instance = await FormoAnalytics.init('key', { autocapture: false }); + + expect(instance.isAutocaptureEnabled('connect')).toBe(false); + expect(instance.isAutocaptureEnabled('transaction')).toBe(false); + }); + + it('should return false for specific disabled event types', async () => { + const instance = await FormoAnalytics.init('key', { + autocapture: { + connect: true, + disconnect: false, + signature: true, + transaction: false, + chain: true, + }, + }); + + expect(instance.isAutocaptureEnabled('connect')).toBe(true); + expect(instance.isAutocaptureEnabled('disconnect')).toBe(false); + expect(instance.isAutocaptureEnabled('transaction')).toBe(false); + }); + }); + + describe('reset()', () => { + it('should clear currentUserId', () => { + analytics.currentUserId = 'user-123'; + + analytics.reset(); + + expect(analytics.currentUserId).toBeUndefined(); + }); + + it('should remove storage keys', () => { + analytics.reset(); + + expect(mockStorageInstance.remove).toHaveBeenCalled(); + }); + }); + + describe('flush()', () => { + it('should call eventQueue.flush()', async () => { + await analytics.flush(); + + expect(mockEventQueue.flush).toHaveBeenCalled(); + }); + }); + + describe('cleanup()', () => { + it('should call eventQueue.cleanup()', async () => { + await analytics.cleanup(); + + expect(mockEventQueue.cleanup).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/__tests__/address.test.ts b/src/__tests__/address.test.ts new file mode 100644 index 0000000..fcc6940 --- /dev/null +++ b/src/__tests__/address.test.ts @@ -0,0 +1,139 @@ +import { + isValidAddress, + toChecksumAddress, + getValidAddress, + isBlockedAddress, +} from '../utils/address'; + +describe('address utilities', () => { + describe('isValidAddress()', () => { + it('should return true for valid lowercase address', () => { + expect(isValidAddress('0x742d35cc6634c0532925a3b844bc9e7595f3f6d2')).toBe(true); + }); + + it('should return true for valid uppercase address', () => { + expect(isValidAddress('0x742D35CC6634C0532925A3B844BC9E7595F3F6D2')).toBe(true); + }); + + it('should return true for valid mixed case address', () => { + expect(isValidAddress('0x742d35Cc6634C0532925a3b844Bc9e7595F3f6d2')).toBe(true); + }); + + it('should return false for address without 0x prefix', () => { + expect(isValidAddress('742d35cc6634c0532925a3b844bc9e7595f3f6d2')).toBe(false); + }); + + it('should return false for address that is too short', () => { + expect(isValidAddress('0x742d35cc6634c0532925a3b844bc9e7595f3f6')).toBe(false); + }); + + it('should return false for address that is too long', () => { + expect(isValidAddress('0x742d35cc6634c0532925a3b844bc9e7595f3f6d2a')).toBe(false); + }); + + it('should return false for address with invalid characters', () => { + expect(isValidAddress('0x742d35cc6634c0532925a3b844bc9e7595f3f6gz')).toBe(false); + }); + + it('should return false for empty string', () => { + expect(isValidAddress('')).toBe(false); + }); + + it('should return false for null/undefined', () => { + expect(isValidAddress(null as unknown as string)).toBe(false); + expect(isValidAddress(undefined as unknown as string)).toBe(false); + }); + + it('should return false for non-string values', () => { + expect(isValidAddress(123 as unknown as string)).toBe(false); + expect(isValidAddress({} as unknown as string)).toBe(false); + }); + }); + + describe('toChecksumAddress()', () => { + it('should convert to EIP-55 checksum format', () => { + const input = '0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed'; + const expected = '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'; + expect(toChecksumAddress(input)).toBe(expected); + }); + + it('should handle already checksummed address', () => { + const checksummed = '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'; + expect(toChecksumAddress(checksummed)).toBe(checksummed); + }); + + it('should handle uppercase address', () => { + const uppercase = '0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED'; + const expected = '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'; + expect(toChecksumAddress(uppercase)).toBe(expected); + }); + + it('should return invalid address unchanged', () => { + const invalid = 'not-an-address'; + expect(toChecksumAddress(invalid)).toBe(invalid); + }); + + it('should handle zero address', () => { + const zeroAddress = '0x0000000000000000000000000000000000000000'; + expect(toChecksumAddress(zeroAddress)).toBe(zeroAddress); + }); + + it('should handle well-known addresses correctly', () => { + // Vitalik's address (well-known checksum) + const input = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'; + const expected = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; + expect(toChecksumAddress(input)).toBe(expected); + }); + }); + + describe('getValidAddress()', () => { + it('should return trimmed address for valid input', () => { + const input = ' 0x742d35cc6634c0532925a3b844bc9e7595f3f6d2 '; + expect(getValidAddress(input)).toBe('0x742d35cc6634c0532925a3b844bc9e7595f3f6d2'); + }); + + it('should return null for invalid address', () => { + expect(getValidAddress('invalid')).toBeNull(); + }); + + it('should return null for empty string', () => { + expect(getValidAddress('')).toBeNull(); + }); + + it('should return null for null', () => { + expect(getValidAddress(null)).toBeNull(); + }); + + it('should return null for undefined', () => { + expect(getValidAddress(undefined)).toBeNull(); + }); + + it('should return valid address without modification', () => { + const valid = '0x742d35Cc6634C0532925a3b844Bc9e7595F3f6d2'; + expect(getValidAddress(valid)).toBe(valid); + }); + }); + + describe('isBlockedAddress()', () => { + it('should return true for zero address', () => { + expect(isBlockedAddress('0x0000000000000000000000000000000000000000')).toBe(true); + }); + + it('should return true for dead address', () => { + expect(isBlockedAddress('0x000000000000000000000000000000000000dead')).toBe(true); + }); + + it('should return true for blocked addresses regardless of case', () => { + expect(isBlockedAddress('0x0000000000000000000000000000000000000000')).toBe(true); + expect(isBlockedAddress('0x000000000000000000000000000000000000DEAD')).toBe(true); + }); + + it('should return false for normal addresses', () => { + expect(isBlockedAddress('0x742d35cc6634c0532925a3b844bc9e7595f3f6d2')).toBe(false); + }); + + it('should return false for addresses similar to blocked ones', () => { + expect(isBlockedAddress('0x0000000000000000000000000000000000000001')).toBe(false); + }); + }); +}); diff --git a/src/__tests__/consent.test.ts b/src/__tests__/consent.test.ts new file mode 100644 index 0000000..d8639de --- /dev/null +++ b/src/__tests__/consent.test.ts @@ -0,0 +1,112 @@ +import { + setConsentFlag, + getConsentFlag, + removeConsentFlag, +} from '../lib/consent'; +import { storage } from '../lib/storage'; + +// Mock the storage module +jest.mock('../lib/storage', () => ({ + storage: jest.fn(() => ({ + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + })), +})); + +describe('consent utilities', () => { + let mockStorage: { + get: jest.Mock; + set: jest.Mock; + remove: jest.Mock; + }; + + beforeEach(() => { + mockStorage = { + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + }; + (storage as jest.Mock).mockReturnValue(mockStorage); + }); + + describe('setConsentFlag()', () => { + it('should set a consent flag in storage', () => { + setConsentFlag('write-key', 'opt_out', 'true'); + + expect(mockStorage.set).toHaveBeenCalledWith('consent_opt_out', 'true'); + }); + + it('should handle different flag values', () => { + setConsentFlag('write-key', 'tracking', 'false'); + + expect(mockStorage.set).toHaveBeenCalledWith('consent_tracking', 'false'); + }); + + it('should not throw when storage fails', () => { + mockStorage.set.mockImplementation(() => { + throw new Error('Storage error'); + }); + + expect(() => setConsentFlag('write-key', 'opt_out', 'true')).not.toThrow(); + }); + }); + + describe('getConsentFlag()', () => { + it('should get a consent flag from storage', () => { + mockStorage.get.mockReturnValue('true'); + + const result = getConsentFlag('write-key', 'opt_out'); + + expect(mockStorage.get).toHaveBeenCalledWith('consent_opt_out'); + expect(result).toBe('true'); + }); + + it('should return null when flag does not exist', () => { + mockStorage.get.mockReturnValue(null); + + const result = getConsentFlag('write-key', 'opt_out'); + + expect(result).toBeNull(); + }); + + it('should return null when storage fails', () => { + mockStorage.get.mockImplementation(() => { + throw new Error('Storage error'); + }); + + const result = getConsentFlag('write-key', 'opt_out'); + + expect(result).toBeNull(); + }); + }); + + describe('removeConsentFlag()', () => { + it('should remove a consent flag from storage', () => { + removeConsentFlag('write-key', 'opt_out'); + + expect(mockStorage.remove).toHaveBeenCalledWith('consent_opt_out'); + }); + + it('should not throw when storage fails', () => { + mockStorage.remove.mockImplementation(() => { + throw new Error('Storage error'); + }); + + expect(() => removeConsentFlag('write-key', 'opt_out')).not.toThrow(); + }); + }); + + describe('key generation', () => { + it('should use consistent key format', () => { + setConsentFlag('my-key', 'analytics', 'enabled'); + getConsentFlag('my-key', 'analytics'); + removeConsentFlag('my-key', 'analytics'); + + // All operations should use the same key format + expect(mockStorage.set).toHaveBeenCalledWith('consent_analytics', 'enabled'); + expect(mockStorage.get).toHaveBeenCalledWith('consent_analytics'); + expect(mockStorage.remove).toHaveBeenCalledWith('consent_analytics'); + }); + }); +}); diff --git a/src/__tests__/hash.test.ts b/src/__tests__/hash.test.ts new file mode 100644 index 0000000..66930fd --- /dev/null +++ b/src/__tests__/hash.test.ts @@ -0,0 +1,83 @@ +import { hash, generateUUID } from '../utils/hash'; + +describe('hash utilities', () => { + describe('hash()', () => { + it('should return a 64-character hex string (full SHA-256)', async () => { + const result = await hash('test input'); + expect(result).toHaveLength(64); + expect(result).toMatch(/^[a-f0-9]{64}$/); + }); + + it('should produce consistent hashes for the same input', async () => { + const input = 'consistent input'; + const hash1 = await hash(input); + const hash2 = await hash(input); + expect(hash1).toBe(hash2); + }); + + it('should produce different hashes for different inputs', async () => { + const hash1 = await hash('input 1'); + const hash2 = await hash('input 2'); + expect(hash1).not.toBe(hash2); + }); + + it('should handle empty string', async () => { + const result = await hash(''); + expect(result).toHaveLength(64); + expect(result).toMatch(/^[a-f0-9]{64}$/); + }); + + it('should handle unicode characters', async () => { + const result = await hash('Hello 世界 🌍'); + expect(result).toHaveLength(64); + expect(result).toMatch(/^[a-f0-9]{64}$/); + }); + + it('should handle long strings', async () => { + const longString = 'a'.repeat(10000); + const result = await hash(longString); + expect(result).toHaveLength(64); + expect(result).toMatch(/^[a-f0-9]{64}$/); + }); + + it('should match known SHA-256 hash', async () => { + // SHA-256 of "hello" is well-known + const result = await hash('hello'); + expect(result).toBe('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'); + }); + }); + + describe('generateUUID()', () => { + it('should return a valid UUID v4 format', () => { + const uuid = generateUUID(); + // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + expect(uuid).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + ); + }); + + it('should have version 4 in the correct position', () => { + const uuid = generateUUID(); + expect(uuid.charAt(14)).toBe('4'); + }); + + it('should have valid variant bits', () => { + const uuid = generateUUID(); + // Position 19 should be 8, 9, a, or b + expect(['8', '9', 'a', 'b']).toContain(uuid.charAt(19)); + }); + + it('should generate unique UUIDs', () => { + const uuids = new Set(); + for (let i = 0; i < 100; i++) { + uuids.add(generateUUID()); + } + expect(uuids.size).toBe(100); + }); + + it('should have the correct length (36 characters with hyphens)', () => { + const uuid = generateUUID(); + expect(uuid).toHaveLength(36); + }); + }); +}); diff --git a/src/__tests__/helpers.test.ts b/src/__tests__/helpers.test.ts new file mode 100644 index 0000000..9733ae7 --- /dev/null +++ b/src/__tests__/helpers.test.ts @@ -0,0 +1,267 @@ +import { + clampNumber, + isUndefined, + toSnakeCase, + mergeDeepRight, + getActionDescriptor, + isNetworkError, +} from '../utils/helpers'; + +describe('helper utilities', () => { + describe('clampNumber()', () => { + it('should return value when within range', () => { + expect(clampNumber(5, 10, 0)).toBe(5); + }); + + it('should return min when value is below min', () => { + expect(clampNumber(-5, 10, 0)).toBe(0); + }); + + it('should return max when value is above max', () => { + expect(clampNumber(15, 10, 0)).toBe(10); + }); + + it('should handle equal min and max', () => { + expect(clampNumber(5, 5, 5)).toBe(5); + }); + + it('should handle negative ranges', () => { + expect(clampNumber(-15, -5, -20)).toBe(-15); + expect(clampNumber(-25, -5, -20)).toBe(-20); + expect(clampNumber(0, -5, -20)).toBe(-5); + }); + + it('should handle decimal values', () => { + expect(clampNumber(0.5, 1.0, 0.0)).toBe(0.5); + expect(clampNumber(-0.1, 1.0, 0.0)).toBe(0.0); + expect(clampNumber(1.5, 1.0, 0.0)).toBe(1.0); + }); + }); + + describe('isUndefined()', () => { + it('should return true for undefined', () => { + expect(isUndefined(undefined)).toBe(true); + }); + + it('should return false for null', () => { + expect(isUndefined(null)).toBe(false); + }); + + it('should return false for empty string', () => { + expect(isUndefined('')).toBe(false); + }); + + it('should return false for 0', () => { + expect(isUndefined(0)).toBe(false); + }); + + it('should return false for false', () => { + expect(isUndefined(false)).toBe(false); + }); + + it('should return false for objects', () => { + expect(isUndefined({})).toBe(false); + expect(isUndefined([])).toBe(false); + }); + }); + + describe('toSnakeCase()', () => { + it('should convert simple camelCase keys', () => { + const input = { firstName: 'John', lastName: 'Doe' }; + const expected = { first_name: 'John', last_name: 'Doe' }; + expect(toSnakeCase(input)).toEqual(expected); + }); + + it('should handle PascalCase', () => { + const input = { FirstName: 'John' }; + const expected = { first_name: 'John' }; + expect(toSnakeCase(input)).toEqual(expected); + }); + + it('should handle acronyms correctly', () => { + const input = { userID: 123, xmlParser: true, getHTTPResponse: 'ok' }; + const expected = { user_id: 123, xml_parser: true, get_http_response: 'ok' }; + expect(toSnakeCase(input)).toEqual(expected); + }); + + it('should handle nested objects', () => { + const input = { + userInfo: { + firstName: 'John', + addressDetails: { streetName: 'Main St' }, + }, + }; + const expected = { + user_info: { + first_name: 'John', + address_details: { street_name: 'Main St' }, + }, + }; + expect(toSnakeCase(input)).toEqual(expected); + }); + + it('should handle arrays with objects', () => { + const input = { + users: [ + { firstName: 'John' }, + { firstName: 'Jane' }, + ], + }; + const expected = { + users: [ + { first_name: 'John' }, + { first_name: 'Jane' }, + ], + }; + expect(toSnakeCase(input)).toEqual(expected); + }); + + it('should preserve non-object array items', () => { + const input = { items: ['one', 'two', 3, null] }; + const expected = { items: ['one', 'two', 3, null] }; + expect(toSnakeCase(input)).toEqual(expected); + }); + + it('should preserve Date objects', () => { + const date = new Date('2024-01-01'); + const input = { createdAt: date }; + const result = toSnakeCase(input) as Record; + expect(result['created_at']).toBe(date); + }); + + it('should handle empty objects', () => { + expect(toSnakeCase({})).toEqual({}); + }); + + it('should handle already snake_case keys', () => { + const input = { already_snake: 'value' }; + expect(toSnakeCase(input)).toEqual({ already_snake: 'value' }); + }); + }); + + describe('mergeDeepRight()', () => { + it('should merge simple objects', () => { + const target = { a: 1, b: 2 }; + const source = { b: 3, c: 4 }; + expect(mergeDeepRight(target, source)).toEqual({ a: 1, b: 3, c: 4 }); + }); + + it('should merge nested objects deeply', () => { + const target = { a: { x: 1, y: 2 }, b: 3 } as Record; + const source = { a: { y: 3, z: 4 } } as Record; + expect(mergeDeepRight(target, source)).toEqual({ + a: { x: 1, y: 3, z: 4 }, + b: 3, + }); + }); + + it('should not mutate original objects', () => { + const target = { a: 1 } as Record; + const source = { b: 2 } as Record; + const result = mergeDeepRight(target, source); + expect(target).toEqual({ a: 1 }); + expect(source).toEqual({ b: 2 }); + expect(result).toEqual({ a: 1, b: 2 }); + }); + + it('should handle arrays by replacing', () => { + const target = { items: [1, 2, 3] }; + const source = { items: [4, 5] }; + expect(mergeDeepRight(target, source)).toEqual({ items: [4, 5] }); + }); + + it('should handle null values in source', () => { + const target = { a: 1, b: { c: 2 } } as Record; + const source = { b: null } as Record; + const result = mergeDeepRight(target, source); + expect(result.b).toBeNull(); + }); + + it('should ignore undefined values in source', () => { + const target = { a: 1, b: 2 }; + const source = { b: undefined }; + expect(mergeDeepRight(target, source)).toEqual({ a: 1, b: 2 }); + }); + + it('should handle empty objects', () => { + expect(mergeDeepRight({}, { a: 1 })).toEqual({ a: 1 }); + expect(mergeDeepRight({ a: 1 }, {})).toEqual({ a: 1 }); + }); + }); + + describe('getActionDescriptor()', () => { + it('should return track:event for track type with event property', () => { + expect(getActionDescriptor('track', { event: 'button_click' })).toBe( + 'track:button_click' + ); + }); + + it('should return screen:name for screen type with name property', () => { + expect(getActionDescriptor('screen', { name: 'HomeScreen' })).toBe( + 'screen:HomeScreen' + ); + }); + + it('should return type for track without event', () => { + expect(getActionDescriptor('track', { other: 'value' })).toBe('track'); + }); + + it('should return type for screen without name', () => { + expect(getActionDescriptor('screen', { other: 'value' })).toBe('screen'); + }); + + it('should return type for other event types', () => { + expect(getActionDescriptor('connect', { address: '0x...' })).toBe('connect'); + expect(getActionDescriptor('disconnect', {})).toBe('disconnect'); + }); + + it('should handle null/undefined properties', () => { + expect(getActionDescriptor('track', null)).toBe('track'); + expect(getActionDescriptor('screen', undefined)).toBe('screen'); + }); + }); + + describe('isNetworkError()', () => { + it('should return true for "Network request failed"', () => { + expect(isNetworkError(new Error('Network request failed'))).toBe(true); + }); + + it('should return true for "Failed to fetch"', () => { + expect(isNetworkError(new Error('Failed to fetch'))).toBe(true); + }); + + it('should return true for "Network Error"', () => { + expect(isNetworkError(new Error('Network Error'))).toBe(true); + }); + + it('should return true for timeout errors', () => { + expect(isNetworkError(new Error('Request timeout'))).toBe(true); + expect(isNetworkError(new Error('ETIMEDOUT'))).toBe(true); + }); + + it('should return true for connection errors', () => { + expect(isNetworkError(new Error('ECONNREFUSED'))).toBe(true); + expect(isNetworkError(new Error('ENOTFOUND'))).toBe(true); + }); + + it('should be case insensitive', () => { + expect(isNetworkError(new Error('NETWORK REQUEST FAILED'))).toBe(true); + expect(isNetworkError(new Error('network error'))).toBe(true); + }); + + it('should return false for other errors', () => { + expect(isNetworkError(new Error('Invalid JSON'))).toBe(false); + expect(isNetworkError(new Error('Unauthorized'))).toBe(false); + }); + + it('should return false for null/undefined', () => { + expect(isNetworkError(null)).toBe(false); + expect(isNetworkError(undefined)).toBe(false); + }); + + it('should handle string errors', () => { + expect(isNetworkError('Network request failed')).toBe(true); + expect(isNetworkError('Some other error')).toBe(false); + }); + }); +}); diff --git a/src/__tests__/session.test.ts b/src/__tests__/session.test.ts new file mode 100644 index 0000000..2640e0e --- /dev/null +++ b/src/__tests__/session.test.ts @@ -0,0 +1,151 @@ +import { FormoAnalyticsSession } from '../lib/session'; +import { storage } from '../lib/storage'; + +// Mock the storage module +jest.mock('../lib/storage', () => ({ + storage: jest.fn(() => ({ + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + })), +})); + +describe('FormoAnalyticsSession', () => { + let session: FormoAnalyticsSession; + let mockStorage: { + get: jest.Mock; + set: jest.Mock; + remove: jest.Mock; + }; + + beforeEach(() => { + mockStorage = { + get: jest.fn().mockReturnValue(null), + set: jest.fn(), + remove: jest.fn(), + }; + (storage as jest.Mock).mockReturnValue(mockStorage); + session = new FormoAnalyticsSession(); + }); + + describe('wallet detection', () => { + it('should return false for undetected wallet', () => { + expect(session.isWalletDetected('io.metamask')).toBe(false); + }); + + it('should return true after marking wallet as detected', () => { + session.markWalletDetected('io.metamask'); + expect(session.isWalletDetected('io.metamask')).toBe(true); + }); + + it('should track multiple wallets independently', () => { + session.markWalletDetected('io.metamask'); + session.markWalletDetected('com.coinbase.wallet'); + + expect(session.isWalletDetected('io.metamask')).toBe(true); + expect(session.isWalletDetected('com.coinbase.wallet')).toBe(true); + expect(session.isWalletDetected('com.phantom')).toBe(false); + }); + + it('should save to storage when marking detected', () => { + session.markWalletDetected('io.metamask'); + expect(mockStorage.set).toHaveBeenCalled(); + }); + }); + + describe('wallet identification', () => { + const address = '0x742d35cc6634c0532925a3b844bc9e7595f3f6d2'; + const rdns = 'io.metamask'; + + it('should return false for unidentified wallet', () => { + expect(session.isWalletIdentified(address, rdns)).toBe(false); + }); + + it('should return true after marking wallet as identified', () => { + session.markWalletIdentified(address, rdns); + expect(session.isWalletIdentified(address, rdns)).toBe(true); + }); + + it('should be case insensitive for addresses', () => { + session.markWalletIdentified(address.toLowerCase(), rdns); + expect(session.isWalletIdentified(address.toUpperCase(), rdns)).toBe(true); + }); + + it('should track different address+rdns combinations independently', () => { + const address2 = '0x1234567890123456789012345678901234567890'; + + session.markWalletIdentified(address, rdns); + session.markWalletIdentified(address2, 'com.coinbase.wallet'); + + expect(session.isWalletIdentified(address, rdns)).toBe(true); + expect(session.isWalletIdentified(address2, 'com.coinbase.wallet')).toBe(true); + expect(session.isWalletIdentified(address, 'com.coinbase.wallet')).toBe(false); + expect(session.isWalletIdentified(address2, rdns)).toBe(false); + }); + + it('should save to storage when marking identified', () => { + session.markWalletIdentified(address, rdns); + expect(mockStorage.set).toHaveBeenCalled(); + }); + }); + + describe('clear()', () => { + it('should clear all detected and identified wallets', () => { + session.markWalletDetected('io.metamask'); + session.markWalletIdentified('0x123', 'io.metamask'); + + session.clear(); + + expect(session.isWalletDetected('io.metamask')).toBe(false); + expect(session.isWalletIdentified('0x123', 'io.metamask')).toBe(false); + }); + + it('should remove from storage when clearing', () => { + session.clear(); + expect(mockStorage.remove).toHaveBeenCalledTimes(2); + }); + }); + + describe('loading from storage', () => { + it('should load detected wallets from storage', () => { + mockStorage.get.mockImplementation((key: string) => { + if (key.includes('detected')) { + return JSON.stringify(['io.metamask', 'com.coinbase.wallet']); + } + return null; + }); + + const loadedSession = new FormoAnalyticsSession(); + + expect(loadedSession.isWalletDetected('io.metamask')).toBe(true); + expect(loadedSession.isWalletDetected('com.coinbase.wallet')).toBe(true); + }); + + it('should load identified wallets from storage', () => { + mockStorage.get.mockImplementation((key: string) => { + if (key.includes('identified')) { + return JSON.stringify(['0x123:io.metamask']); + } + return null; + }); + + const loadedSession = new FormoAnalyticsSession(); + + expect(loadedSession.isWalletIdentified('0x123', 'io.metamask')).toBe(true); + }); + + it('should handle invalid JSON in storage gracefully', () => { + mockStorage.get.mockReturnValue('invalid json'); + + expect(() => new FormoAnalyticsSession()).not.toThrow(); + }); + + it('should handle empty storage', () => { + mockStorage.get.mockReturnValue(null); + + const loadedSession = new FormoAnalyticsSession(); + + expect(loadedSession.isWalletDetected('anything')).toBe(false); + }); + }); +}); diff --git a/src/__tests__/storage.test.ts b/src/__tests__/storage.test.ts new file mode 100644 index 0000000..cb60cef --- /dev/null +++ b/src/__tests__/storage.test.ts @@ -0,0 +1,121 @@ +import MemoryStorage from '../lib/storage/MemoryStorage'; + +describe('MemoryStorage', () => { + let storage: MemoryStorage; + + beforeEach(() => { + storage = new MemoryStorage('test-write-key'); + }); + + describe('isAvailable()', () => { + it('should always return true', () => { + expect(storage.isAvailable()).toBe(true); + }); + }); + + describe('getKey()', () => { + it('should generate prefixed key', () => { + const key = storage.getKey('myKey'); + expect(key).toBe('formo_rn_test-write-key_myKey'); + }); + + it('should handle special characters in key', () => { + const key = storage.getKey('my-special_key.123'); + expect(key).toBe('formo_rn_test-write-key_my-special_key.123'); + }); + }); + + describe('set() and get()', () => { + it('should store and retrieve a value', () => { + storage.set('testKey', 'testValue'); + expect(storage.get('testKey')).toBe('testValue'); + }); + + it('should return null for non-existent key', () => { + expect(storage.get('nonExistent')).toBeNull(); + }); + + it('should overwrite existing value', () => { + storage.set('key', 'value1'); + storage.set('key', 'value2'); + expect(storage.get('key')).toBe('value2'); + }); + + it('should store empty string', () => { + storage.set('emptyKey', ''); + expect(storage.get('emptyKey')).toBe(''); + }); + + it('should handle JSON strings', () => { + const json = JSON.stringify({ foo: 'bar', num: 123 }); + storage.set('jsonKey', json); + expect(JSON.parse(storage.get('jsonKey')!)).toEqual({ foo: 'bar', num: 123 }); + }); + }); + + describe('setAsync() and getAsync()', () => { + it('should store and retrieve a value asynchronously', async () => { + await storage.setAsync('asyncKey', 'asyncValue'); + const value = await storage.getAsync('asyncKey'); + expect(value).toBe('asyncValue'); + }); + + it('should return null for non-existent key', async () => { + const value = await storage.getAsync('nonExistent'); + expect(value).toBeNull(); + }); + }); + + describe('remove()', () => { + it('should remove an existing key', () => { + storage.set('toRemove', 'value'); + expect(storage.get('toRemove')).toBe('value'); + + storage.remove('toRemove'); + expect(storage.get('toRemove')).toBeNull(); + }); + + it('should not throw when removing non-existent key', () => { + expect(() => storage.remove('nonExistent')).not.toThrow(); + }); + }); + + describe('removeAsync()', () => { + it('should remove an existing key asynchronously', async () => { + await storage.setAsync('toRemove', 'value'); + await storage.removeAsync('toRemove'); + expect(await storage.getAsync('toRemove')).toBeNull(); + }); + }); + + describe('clear()', () => { + it('should remove all stored values', () => { + storage.set('key1', 'value1'); + storage.set('key2', 'value2'); + storage.set('key3', 'value3'); + + storage.clear(); + + expect(storage.get('key1')).toBeNull(); + expect(storage.get('key2')).toBeNull(); + expect(storage.get('key3')).toBeNull(); + }); + + it('should not throw when storage is already empty', () => { + expect(() => storage.clear()).not.toThrow(); + }); + }); + + describe('isolation between instances', () => { + it('should isolate data between different write keys', () => { + const storage1 = new MemoryStorage('key1'); + const storage2 = new MemoryStorage('key2'); + + storage1.set('shared', 'value1'); + storage2.set('shared', 'value2'); + + expect(storage1.get('shared')).toBe('value1'); + expect(storage2.get('shared')).toBe('value2'); + }); + }); +}); diff --git a/src/constants/config.ts b/src/constants/config.ts new file mode 100644 index 0000000..4a7ab42 --- /dev/null +++ b/src/constants/config.ts @@ -0,0 +1,62 @@ +export const EVENTS_API_ORIGIN = "https://events.formo.so"; +export const EVENTS_API_HOST = `${EVENTS_API_ORIGIN}/v0/raw_events`; + +export const EVENTS_API_REQUEST_HEADER = (writeKey: string) => ({ + "Content-Type": "application/json", + Authorization: `Bearer ${writeKey}`, +}); + +// Timezone to country mapping +export const COUNTRY_LIST: Record = { + // Africa + "Africa/Abidjan": "CI", + "Africa/Accra": "GH", + "Africa/Addis_Ababa": "ET", + "Africa/Algiers": "DZ", + "Africa/Cairo": "EG", + "Africa/Casablanca": "MA", + "Africa/Johannesburg": "ZA", + "Africa/Lagos": "NG", + "Africa/Nairobi": "KE", + // America + "America/Anchorage": "US", + "America/Argentina/Buenos_Aires": "AR", + "America/Bogota": "CO", + "America/Chicago": "US", + "America/Denver": "US", + "America/Los_Angeles": "US", + "America/Mexico_City": "MX", + "America/New_York": "US", + "America/Phoenix": "US", + "America/Sao_Paulo": "BR", + "America/Toronto": "CA", + "America/Vancouver": "CA", + // Asia + "Asia/Bangkok": "TH", + "Asia/Dubai": "AE", + "Asia/Ho_Chi_Minh": "VN", + "Asia/Hong_Kong": "HK", + "Asia/Jakarta": "ID", + "Asia/Kolkata": "IN", + "Asia/Manila": "PH", + "Asia/Seoul": "KR", + "Asia/Shanghai": "CN", + "Asia/Singapore": "SG", + "Asia/Taipei": "TW", + "Asia/Tokyo": "JP", + // Australia + "Australia/Melbourne": "AU", + "Australia/Sydney": "AU", + // Europe + "Europe/Amsterdam": "NL", + "Europe/Berlin": "DE", + "Europe/London": "GB", + "Europe/Madrid": "ES", + "Europe/Moscow": "RU", + "Europe/Paris": "FR", + "Europe/Rome": "IT", + "Europe/Zurich": "CH", + // Pacific + "Pacific/Auckland": "NZ", + "Pacific/Honolulu": "US", +}; diff --git a/src/constants/events.ts b/src/constants/events.ts new file mode 100644 index 0000000..5770194 --- /dev/null +++ b/src/constants/events.ts @@ -0,0 +1,26 @@ +export enum EventType { + PAGE = "page", + SCREEN = "screen", + IDENTIFY = "identify", + DETECT = "detect", + CONNECT = "connect", + DISCONNECT = "disconnect", + CHAIN = "chain", + SIGNATURE = "signature", + TRANSACTION = "transaction", + TRACK = "track", +} + +export enum EventChannel { + WEB = "web", + MOBILE = "mobile", + SERVER = "server", + SOURCE = "source", +} + +export type TEventType = Lowercase; +export type TEventChannel = Lowercase; + +// React Native SDK uses mobile channel +export const CHANNEL: TEventChannel = "mobile"; +export const VERSION = "1"; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..1e65cd7 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,3 @@ +export * from "./events"; +export * from "./config"; +export * from "./storage"; diff --git a/src/constants/storage.ts b/src/constants/storage.ts new file mode 100644 index 0000000..df1ead0 --- /dev/null +++ b/src/constants/storage.ts @@ -0,0 +1,14 @@ +// Storage keys for React Native SDK +export const STORAGE_PREFIX = "formo_rn_"; + +// Local storage keys (persistent) +export const LOCAL_ANONYMOUS_ID_KEY = "anonymous_id"; + +// Session storage keys (cleared on app restart) +export const SESSION_USER_ID_KEY = "user_id"; +export const SESSION_TRAFFIC_SOURCE_KEY = "traffic_source"; +export const SESSION_WALLET_DETECTED_KEY = "wallet_detected"; +export const SESSION_WALLET_IDENTIFIED_KEY = "wallet_identified"; + +// Consent keys +export const CONSENT_OPT_OUT_KEY = "opt_out_tracking"; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0b72fb1 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,55 @@ +/** + * Formo Analytics SDK for React Native + * + * Track wallet events and user analytics in mobile dApps + * + * @example + * ```tsx + * import AsyncStorage from '@react-native-async-storage/async-storage'; + * import { FormoAnalyticsProvider, useFormo } from '@formo/react-native-analytics'; + * + * // Wrap your app with the provider + * function App() { + * return ( + * + * + * + * ); + * } + * + * // Use the hook in your components + * function MyScreen() { + * const formo = useFormo(); + * + * useEffect(() => { + * formo.screen('Home'); + * }, []); + * + * return ...; + * } + * ``` + */ + +// Main exports +export { FormoAnalytics } from "./FormoAnalytics"; +export { + FormoAnalyticsProvider, + FormoAnalyticsContext, + useFormo, +} from "./FormoAnalyticsProvider"; + +// Types +export * from "./types"; + +// Event types for manual event tracking +export { SignatureStatus, TransactionStatus } from "./types/events"; + +// Storage types for custom storage implementations +export type { AsyncStorageInterface } from "./lib/storage"; diff --git a/src/lib/consent/index.ts b/src/lib/consent/index.ts new file mode 100644 index 0000000..b30816f --- /dev/null +++ b/src/lib/consent/index.ts @@ -0,0 +1,52 @@ +import { storage } from "../storage"; +import { logger } from "../logger"; + +/** + * Get consent storage key + * Note: The storage adapter already prefixes keys with formo_rn_{writeKey}_ + * so we just need a simple consent prefix here + */ +function getConsentKey(_writeKey: string, key: string): string { + return `consent_${key}`; +} + +/** + * Set a consent flag + */ +export function setConsentFlag( + writeKey: string, + key: string, + value: string +): void { + try { + const consentKey = getConsentKey(writeKey, key); + storage().set(consentKey, value); + } catch (error) { + logger.error("Consent: Failed to set flag", error); + } +} + +/** + * Get a consent flag + */ +export function getConsentFlag(writeKey: string, key: string): string | null { + try { + const consentKey = getConsentKey(writeKey, key); + return storage().get(consentKey); + } catch (error) { + logger.error("Consent: Failed to get flag", error); + return null; + } +} + +/** + * Remove a consent flag + */ +export function removeConsentFlag(writeKey: string, key: string): void { + try { + const consentKey = getConsentKey(writeKey, key); + storage().remove(consentKey); + } catch (error) { + logger.error("Consent: Failed to remove flag", error); + } +} diff --git a/src/lib/event/EventFactory.ts b/src/lib/event/EventFactory.ts new file mode 100644 index 0000000..987b697 --- /dev/null +++ b/src/lib/event/EventFactory.ts @@ -0,0 +1,665 @@ +import { Platform, NativeModules, Dimensions } from "react-native"; +import NetInfo from "@react-native-community/netinfo"; + +// Lazy load device info to handle Expo Go where native modules may not exist +let DeviceInfo: typeof import("react-native-device-info").default | null = null; +let ExpoDevice: typeof import("expo-device") | null = null; +let ExpoApplication: typeof import("expo-application") | null = null; + +// Try to load react-native-device-info (works in bare RN and dev builds) +try { + DeviceInfo = require("react-native-device-info").default; +} catch { + // Not available - try Expo alternatives +} + +// Try to load Expo modules (works in Expo Go and Expo dev builds) +try { + ExpoDevice = require("expo-device"); +} catch { + // Not available +} + +try { + ExpoApplication = require("expo-application"); +} catch { + // Not available +} +import { COUNTRY_LIST, LOCAL_ANONYMOUS_ID_KEY, CHANNEL, VERSION } from "../../constants"; +import { + Address, + APIEvent, + ChainID, + IFormoEvent, + IFormoEventContext, + IFormoEventProperties, + Nullable, + Options, + SignatureStatus, + TransactionStatus, +} from "../../types"; +import { + toChecksumAddress, + getValidAddress, + toSnakeCase, + mergeDeepRight, + getStoredTrafficSource, +} from "../../utils"; +import { getCurrentTimeFormatted } from "../../utils/timestamp"; +import { generateUUID } from "../../utils/hash"; +import { logger } from "../logger"; +import { storage } from "../storage"; +import { IEventFactory } from "./types"; + +// SDK version +const SDK_VERSION = "1.0.0"; + +/** + * Generate or retrieve anonymous ID + */ +function generateAnonymousId(key: string): string { + const existing = storage().get(key); + if (existing) { + return existing; + } + + const newId = generateUUID(); + storage().set(key, newId); + return newId; +} + +/** + * Event factory for React Native + * Creates event payloads with mobile-specific context + */ +class EventFactory implements IEventFactory { + private options?: Options; + + constructor(options?: Options) { + this.options = options; + } + + /** + * Get device timezone + */ + private getTimezone(): string { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch (error) { + logger.error("Error resolving timezone:", error); + return ""; + } + } + + /** + * Get location from timezone + */ + private getLocation(): string { + try { + const timezone = this.getTimezone(); + if (timezone in COUNTRY_LIST) { + return COUNTRY_LIST[timezone] ?? timezone; + } + return timezone; + } catch (error) { + logger.error("Error resolving location:", error); + return ""; + } + } + + /** + * Get device language/locale + */ + private getLanguage(): string { + try { + // Try to get the device locale + const locale = + Platform.OS === "ios" + ? NativeModules.SettingsManager?.settings?.AppleLocale || + NativeModules.SettingsManager?.settings?.AppleLanguages?.[0] + : NativeModules.I18nManager?.localeIdentifier; + + return locale || "en"; + } catch (error) { + logger.debug("Error resolving language:", error); + return "en"; + } + } + + /** + * Get screen dimensions + */ + private getScreen(): { + screen_width: number; + screen_height: number; + screen_density: number; + } { + try { + const { width, height, scale } = Dimensions.get("screen"); + return { + screen_width: Math.round(width), + screen_height: Math.round(height), + screen_density: scale, + }; + } catch (error) { + logger.error("Error resolving screen properties:", error); + return { + screen_width: 0, + screen_height: 0, + screen_density: 1, + }; + } + } + + /** + * Get network information + */ + private async getNetworkInfo(): Promise<{ + network_wifi?: boolean; + network_cellular?: boolean; + network_carrier?: string; + }> { + try { + const netState = await NetInfo.fetch(); + + const networkInfo: { + network_wifi?: boolean; + network_cellular?: boolean; + network_carrier?: string; + } = {}; + + // Set connection type flags + if (netState.type === "wifi") { + networkInfo.network_wifi = true; + networkInfo.network_cellular = false; + } else if (netState.type === "cellular") { + networkInfo.network_wifi = false; + networkInfo.network_cellular = true; + + // Get carrier name for cellular connections + if (netState.details && "carrier" in netState.details) { + networkInfo.network_carrier = netState.details.carrier || undefined; + } + } else { + // Other types (ethernet, bluetooth, wimax, vpn, other, unknown, none) + networkInfo.network_wifi = false; + networkInfo.network_cellular = false; + } + + return networkInfo; + } catch (error) { + logger.debug("Error getting network info:", error); + return {}; + } + } + + /** + * Get device information + * Supports both react-native-device-info (bare RN) and expo-device/expo-application (Expo Go) + */ + private async getDeviceInfo(): Promise<{ + os_name: string; + os_version: string; + device_model: string; + device_manufacturer: string; + device_name: string; + device_type: string; + user_agent: string; + app_name: string; + app_version: string; + app_build: string; + app_bundle_id: string; + }> { + // Try react-native-device-info first (bare RN and Expo dev builds) + if (DeviceInfo) { + try { + const [model, manufacturer, deviceName, userAgent, isTablet] = await Promise.all([ + DeviceInfo.getModel(), + DeviceInfo.getManufacturer(), + DeviceInfo.getDeviceName(), + DeviceInfo.getUserAgent(), + DeviceInfo.isTablet(), + ]); + + return { + os_name: Platform.OS, + os_version: DeviceInfo.getSystemVersion(), + device_model: model, + device_manufacturer: manufacturer, + device_name: deviceName, + device_type: isTablet ? "tablet" : "mobile", + user_agent: userAgent, + app_name: DeviceInfo.getApplicationName(), + app_version: DeviceInfo.getVersion(), + app_build: DeviceInfo.getBuildNumber(), + app_bundle_id: DeviceInfo.getBundleId(), + }; + } catch (error) { + logger.debug("Error using react-native-device-info, falling back:", error); + } + } + + // Fall back to Expo modules (Expo Go) + if (ExpoDevice || ExpoApplication) { + try { + const isTablet = ExpoDevice?.deviceType === ExpoDevice?.DeviceType?.TABLET; + return { + os_name: ExpoDevice?.osName || Platform.OS, + os_version: ExpoDevice?.osVersion || String(Platform.Version), + device_model: ExpoDevice?.modelName || "Unknown", + device_manufacturer: ExpoDevice?.manufacturer || "Unknown", + device_name: ExpoDevice?.deviceName || "Unknown Device", + device_type: isTablet ? "tablet" : "mobile", + user_agent: "", + app_name: ExpoApplication?.applicationName || "", + app_version: ExpoApplication?.nativeApplicationVersion || "", + app_build: ExpoApplication?.nativeBuildVersion || "", + app_bundle_id: ExpoApplication?.applicationId || "", + }; + } catch (error) { + logger.debug("Error using Expo device modules:", error); + } + } + + // Final fallback - minimal info from Platform + logger.debug("No device info modules available, using Platform defaults"); + return { + os_name: Platform.OS, + os_version: String(Platform.Version), + device_model: "Unknown", + device_manufacturer: "Unknown", + device_name: "Unknown Device", + device_type: "mobile", + user_agent: "", + app_name: "", + app_version: "", + app_build: "", + app_bundle_id: "", + }; + } + + /** + * Generate context with mobile-specific metadata + */ + private async generateContext( + context?: IFormoEventContext + ): Promise { + const language = this.getLanguage(); + const timezone = this.getTimezone(); + const location = this.getLocation(); + const deviceInfo = await this.getDeviceInfo(); + const networkInfo = await this.getNetworkInfo(); + const screenInfo = this.getScreen(); + + // Get stored traffic source from session (UTM params, referrer from deep links) + const storedTrafficSource = getStoredTrafficSource(); + + const defaultContext: IFormoEventContext = { + locale: language, + timezone, + location, + library_name: "Formo React Native SDK", + library_version: SDK_VERSION, + ...deviceInfo, + ...networkInfo, + ...screenInfo, + // App info from options (overrides auto-detected values) + ...(this.options?.app?.name && { app_name: this.options.app.name }), + ...(this.options?.app?.version && { app_version: this.options.app.version }), + ...(this.options?.app?.build && { app_build: this.options.app.build }), + ...(this.options?.app?.bundleId && { app_bundle_id: this.options.app.bundleId }), + // Traffic source (UTM params, referrer) from session + ...(storedTrafficSource || {}), + }; + + const mergedContext = mergeDeepRight( + defaultContext, + context || {} + ) as IFormoEventContext; + + return mergedContext; + } + + /** + * Create enriched event with common properties + */ + private async getEnrichedEvent( + formoEvent: Partial, + context?: IFormoEventContext + ): Promise { + const commonEventData: Partial = { + context: await this.generateContext(context), + original_timestamp: getCurrentTimeFormatted(), + user_id: formoEvent.user_id, + type: formoEvent.type, + channel: CHANNEL, + version: VERSION, + }; + + commonEventData.anonymous_id = generateAnonymousId(LOCAL_ANONYMOUS_ID_KEY); + + // Handle address - convert undefined to null for consistency + const validAddress = getValidAddress(formoEvent.address); + if (validAddress) { + commonEventData.address = toChecksumAddress(validAddress); + } else { + commonEventData.address = null; + } + + const processedEvent = mergeDeepRight( + formoEvent as Record, + commonEventData as Record + ) as unknown as IFormoEvent; + + if (processedEvent.event === undefined) { + processedEvent.event = null; + } + + if (processedEvent.properties === undefined) { + processedEvent.properties = null; + } + + return toSnakeCase(processedEvent as unknown as Record) as unknown as IFormoEvent; + } + + /** + * Generate screen view event (mobile equivalent of page) + */ + async generateScreenEvent( + name: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const props = { ...(properties ?? {}), name }; + + const screenEvent: Partial = { + properties: props, + type: "screen", + }; + + return this.getEnrichedEvent(screenEvent, context); + } + + async generateDetectWalletEvent( + providerName: string, + rdns: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const detectEvent: Partial = { + properties: { + providerName, + rdns, + ...properties, + }, + type: "detect", + }; + + return this.getEnrichedEvent(detectEvent, context); + } + + async generateIdentifyEvent( + providerName: string, + rdns: string, + address: Nullable
, + userId?: Nullable, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const identifyEvent: Partial = { + properties: { + providerName, + rdns, + ...properties, + }, + user_id: userId, + address, + type: "identify", + }; + + return this.getEnrichedEvent(identifyEvent, context); + } + + async generateConnectEvent( + chainId: ChainID, + address: Address, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const connectEvent: Partial = { + properties: { + chainId, + ...properties, + }, + address, + type: "connect", + }; + + return this.getEnrichedEvent(connectEvent, context); + } + + async generateDisconnectEvent( + chainId?: ChainID, + address?: Address, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const disconnectEvent: Partial = { + properties: { + chainId, + ...properties, + }, + address, + type: "disconnect", + }; + + return this.getEnrichedEvent(disconnectEvent, context); + } + + async generateChainChangedEvent( + chainId: ChainID, + address: Address, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const chainEvent: Partial = { + properties: { + chainId, + ...properties, + }, + address, + type: "chain", + }; + + return this.getEnrichedEvent(chainEvent, context); + } + + async generateSignatureEvent( + status: SignatureStatus, + chainId: ChainID, + address: Address, + message: string, + signatureHash?: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const signatureEvent: Partial = { + properties: { + status, + chainId, + message, + ...(signatureHash && { signatureHash }), + ...properties, + }, + address, + type: "signature", + }; + + return this.getEnrichedEvent(signatureEvent, context); + } + + async generateTransactionEvent( + status: TransactionStatus, + chainId: ChainID, + address: Address, + data: string, + to: string, + value: string, + transactionHash?: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const transactionEvent: Partial = { + properties: { + status, + chainId, + data, + to, + value, + ...(transactionHash && { transactionHash }), + ...properties, + }, + address, + type: "transaction", + }; + + return this.getEnrichedEvent(transactionEvent, context); + } + + async generateTrackEvent( + event: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise { + const trackEvent: Partial = { + properties: { + ...properties, + ...(properties?.revenue !== undefined && { + revenue: Number(properties.revenue), + currency: (typeof properties?.currency === "string" + ? properties.currency + : "USD" + ).toLowerCase(), + }), + ...(properties?.points !== undefined && { + points: Number(properties.points), + }), + ...(properties?.volume !== undefined && { + volume: Number(properties.volume), + }), + }, + event, + type: "track", + }; + + return this.getEnrichedEvent(trackEvent, context); + } + + /** + * Create event from API event type + */ + async create( + event: APIEvent, + address?: Address, + userId?: string + ): Promise { + let formoEvent: Partial = {}; + + switch (event.type) { + case "screen": + formoEvent = await this.generateScreenEvent( + event.name, + event.properties, + event.context + ); + break; + case "detect": + formoEvent = await this.generateDetectWalletEvent( + event.providerName, + event.rdns, + event.properties, + event.context + ); + break; + case "identify": + formoEvent = await this.generateIdentifyEvent( + event.providerName, + event.rdns, + event.address, + event.userId, + event.properties, + event.context + ); + break; + case "chain": + formoEvent = await this.generateChainChangedEvent( + event.chainId, + event.address, + event.properties, + event.context + ); + break; + case "connect": + formoEvent = await this.generateConnectEvent( + event.chainId, + event.address, + event.properties, + event.context + ); + break; + case "disconnect": + formoEvent = await this.generateDisconnectEvent( + event.chainId, + event.address, + event.properties, + event.context + ); + break; + case "signature": + formoEvent = await this.generateSignatureEvent( + event.status, + event.chainId, + event.address, + event.message, + event.signatureHash, + event.properties, + event.context + ); + break; + case "transaction": + formoEvent = await this.generateTransactionEvent( + event.status, + event.chainId, + event.address, + event.data, + event.to, + event.value, + event.transactionHash, + event.properties, + event.context + ); + break; + case "track": + default: + formoEvent = await this.generateTrackEvent( + event.event, + event.properties, + event.context + ); + break; + } + + // Set address if not already set by the specific event generator + if (formoEvent.address === undefined || formoEvent.address === null) { + const validAddress = getValidAddress(address); + formoEvent.address = validAddress + ? toChecksumAddress(validAddress) + : null; + } + formoEvent.user_id = userId || null; + + return formoEvent as IFormoEvent; + } +} + +export { EventFactory }; diff --git a/src/lib/event/EventManager.ts b/src/lib/event/EventManager.ts new file mode 100644 index 0000000..a36830b --- /dev/null +++ b/src/lib/event/EventManager.ts @@ -0,0 +1,50 @@ +import { Address, APIEvent, Options } from "../../types"; +import { logger } from "../logger"; +import { EventFactory } from "./EventFactory"; +import { IEventFactory, IEventManager, IEventQueue } from "./types"; +import { isBlockedAddress } from "../../utils/address"; + +/** + * Event manager for React Native SDK + * Generates valid event payloads and queues them for processing + */ +class EventManager implements IEventManager { + eventQueue: IEventQueue; + eventFactory: IEventFactory; + + constructor(eventQueue: IEventQueue, options?: Options) { + this.eventQueue = eventQueue; + this.eventFactory = new EventFactory(options); + } + + /** + * Add event to queue + */ + async addEvent( + event: APIEvent, + address?: Address, + userId?: string + ): Promise { + const { callback, ..._event } = event; + const formoEvent = await this.eventFactory.create(_event, address, userId); + + // Check if the final event has a blocked address + if (formoEvent.address && isBlockedAddress(formoEvent.address)) { + logger.warn( + `Event blocked: Address ${formoEvent.address} is in the blocked list` + ); + return; + } + + await this.eventQueue.enqueue(formoEvent, (err, _, data) => { + if (err) { + logger.error("Error sending events:", err); + } else { + logger.info(`Events sent successfully: ${(data as unknown[])?.length ?? 0} events`); + } + callback?.(err, _, data); + }); + } +} + +export { EventManager }; diff --git a/src/lib/event/EventQueue.ts b/src/lib/event/EventQueue.ts new file mode 100644 index 0000000..7dad374 --- /dev/null +++ b/src/lib/event/EventQueue.ts @@ -0,0 +1,371 @@ +import { AppState, AppStateStatus } from "react-native"; +import { IFormoEvent, IFormoEventPayload } from "../../types"; +import { EVENTS_API_REQUEST_HEADER } from "../../constants"; +import { + clampNumber, + getActionDescriptor, + millisecondsToSecond, + isNetworkError, +} from "../../utils"; +import { hash } from "../../utils/hash"; +import { toDateHourMinute } from "../../utils/timestamp"; +import { logger } from "../logger"; +import { IEventQueue } from "./types"; + +type QueueItem = { + message: IFormoEventPayload; + callback: (...args: unknown[]) => void; + hash: string; +}; + +type IFormoEventFlushPayload = IFormoEventPayload & { + sent_at: string; +}; + +interface Options { + apiHost: string; + flushAt?: number; + flushInterval?: number; + retryCount?: number; + maxQueueSize?: number; +} + +const DEFAULT_RETRY = 3; +const MAX_RETRY = 5; +const MIN_RETRY = 1; + +const DEFAULT_FLUSH_AT = 20; +const MAX_FLUSH_AT = 20; +const MIN_FLUSH_AT = 1; + +const DEFAULT_QUEUE_SIZE = 1_024 * 500; // 500kB +const MAX_QUEUE_SIZE = 1_024 * 500; // 500kB +const MIN_QUEUE_SIZE = 200; // 200 bytes + +const DEFAULT_FLUSH_INTERVAL = 1_000 * 30; // 30 seconds +const MAX_FLUSH_INTERVAL = 1_000 * 300; // 5 minutes +const MIN_FLUSH_INTERVAL = 1_000 * 10; // 10 seconds + +const noop = () => {}; + +/** + * Event queue for React Native + * Handles batching, flushing, and retries with app lifecycle awareness + */ +export class EventQueue implements IEventQueue { + private writeKey: string; + private apiHost: string; + private queue: QueueItem[] = []; + private timer: ReturnType | null = null; + private flushAt: number; + private flushIntervalMs: number; + private maxQueueSize: number; + private retryCount: number; + private payloadHashes: Set = new Set(); + private flushMutex: Promise = Promise.resolve(); + private appStateSubscription: { remove: () => void } | null = null; + + constructor(writeKey: string, options: Options) { + this.writeKey = writeKey; + this.apiHost = options.apiHost; + this.retryCount = clampNumber( + options.retryCount || DEFAULT_RETRY, + MAX_RETRY, + MIN_RETRY + ); + this.flushAt = clampNumber( + options.flushAt || DEFAULT_FLUSH_AT, + MAX_FLUSH_AT, + MIN_FLUSH_AT + ); + this.maxQueueSize = clampNumber( + options.maxQueueSize || DEFAULT_QUEUE_SIZE, + MAX_QUEUE_SIZE, + MIN_QUEUE_SIZE + ); + this.flushIntervalMs = clampNumber( + options.flushInterval || DEFAULT_FLUSH_INTERVAL, + MAX_FLUSH_INTERVAL, + MIN_FLUSH_INTERVAL + ); + // Set up app state listener for React Native + this.setupAppStateListener(); + } + + /** + * Set up listener for app state changes + * Flush events when app goes to background + */ + private setupAppStateListener(): void { + this.appStateSubscription = AppState.addEventListener( + "change", + this.handleAppStateChange.bind(this) + ); + } + + /** + * Handle app state changes + */ + private handleAppStateChange(nextAppState: AppStateStatus): void { + // Flush when app goes to background or becomes inactive + if (nextAppState === "background" || nextAppState === "inactive") { + logger.debug("EventQueue: App going to background, flushing events"); + this.flush().catch((error) => { + logger.error("EventQueue: Failed to flush on background", error); + }); + } + } + + /** + * Generate message ID for deduplication + */ + private async generateMessageId(event: IFormoEvent): Promise { + const formattedTimestamp = toDateHourMinute( + new Date(event.original_timestamp) + ); + const eventForHashing = { ...event, original_timestamp: formattedTimestamp }; + const eventString = JSON.stringify(eventForHashing); + return hash(eventString); + } + + /** + * Check if event is a duplicate + */ + private isDuplicate(eventId: string): boolean { + if (this.payloadHashes.has(eventId)) return true; + this.payloadHashes.add(eventId); + return false; + } + + /** + * Add event to queue + */ + async enqueue( + event: IFormoEvent, + callback?: (...args: unknown[]) => void + ): Promise { + callback = callback || noop; + + const message_id = await this.generateMessageId(event); + + // Check for duplicate + if (this.isDuplicate(message_id)) { + logger.warn( + `Event already enqueued, try again after ${millisecondsToSecond( + this.flushIntervalMs + )} seconds.` + ); + return; + } + + this.queue.push({ + message: { ...event, message_id }, + callback, + hash: message_id, + }); + + logger.log( + `Event enqueued: ${getActionDescriptor(event.type, event.properties)}` + ); + + const hasReachedFlushAt = this.queue.length >= this.flushAt; + const hasReachedQueueSize = + this.queue.reduce( + (acc, item) => acc + JSON.stringify(item).length, + 0 + ) >= this.maxQueueSize; + + if (hasReachedFlushAt || hasReachedQueueSize) { + // Clear timer to prevent double flush + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + // Flush uses internal mutex to serialize operations + this.flush().catch((error) => { + logger.error("EventQueue: Failed to flush on threshold", error); + }); + return; + } + + if (this.flushIntervalMs && !this.timer) { + this.timer = setTimeout(this.flush.bind(this), this.flushIntervalMs); + } + } + + /** + * Flush events to API + * Uses a mutex to ensure only one flush operation runs at a time, + * preventing race conditions with re-queued items on failure. + */ + async flush(callback?: (...args: unknown[]) => void): Promise { + callback = callback || noop; + + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + // Use mutex to serialize flush operations and prevent race conditions + const previousMutex = this.flushMutex; + let resolveMutex: () => void; + this.flushMutex = new Promise((resolve) => { + resolveMutex = resolve; + }); + + try { + // Wait for any previous flush to complete + await previousMutex; + + if (!this.queue.length) { + callback(); + return; + } + + const items = this.queue.splice(0, this.flushAt); + + const sentAt = new Date().toISOString(); + const data: IFormoEventFlushPayload[] = items.map((item) => ({ + ...item.message, + sent_at: sentAt, + })); + + const done = (err?: Error) => { + items.forEach(({ message, callback: itemCallback }) => + itemCallback(err, message, data) + ); + callback!(err, data); + }; + + try { + await this.sendWithRetry(data); + // Only remove hashes after successful send + items.forEach((item) => this.payloadHashes.delete(item.hash)); + done(); + logger.info(`Events sent successfully: ${data.length} events`); + } catch (err) { + // Re-add items to the front of the queue for retry on next flush + // Note: We intentionally keep hashes in payloadHashes to prevent duplicate + // events from being enqueued while these items are pending retry. + this.queue.unshift(...items); + done(err as Error); + logger.error("Error sending events, re-queued for retry:", err); + throw err; + } + } finally { + resolveMutex!(); + } + } + + /** + * Send events with retry logic + */ + private async sendWithRetry( + data: IFormoEventFlushPayload[], + attempt = 0 + ): Promise { + try { + const response = await fetch(this.apiHost, { + method: "POST", + headers: EVENTS_API_REQUEST_HEADER(this.writeKey), + body: JSON.stringify(data), + }); + + if (!response.ok) { + const shouldRetry = this.shouldRetry(response.status); + if (shouldRetry && attempt < this.retryCount) { + const delay = Math.pow(2, attempt) * 1000; + await new Promise((resolve) => setTimeout(() => resolve(), delay)); + return this.sendWithRetry(data, attempt + 1); + } + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error) { + if (isNetworkError(error) && attempt < this.retryCount) { + const delay = Math.pow(2, attempt) * 1000; + logger.warn(`Network error, retrying in ${delay}ms...`); + await new Promise((resolve) => setTimeout(() => resolve(), delay)); + return this.sendWithRetry(data, attempt + 1); + } + throw error; + } + } + + /** + * Check if error should be retried + */ + private shouldRetry(status: number): boolean { + // Retry on server errors (5xx) and rate limiting (429) + return (status >= 500 && status <= 599) || status === 429; + } + + /** + * Discard all pending events without sending them. + * Used when the user opts out of tracking to prevent queued events + * from being sent after consent is revoked. + */ + public clear(): void { + this.queue = []; + this.payloadHashes.clear(); + + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + logger.debug("EventQueue: Cleared all pending events"); + } + + /** + * Clean up resources, flushing any pending events first + */ + public async cleanup(): Promise { + // Flush all remaining queued events before teardown + // Loop until queue is empty since flush() only sends flushAt events per call + // Safety limit prevents infinite loops if flush silently fails + const maxAttempts = Math.ceil(this.queue.length / this.flushAt) + 3; + let attempts = 0; + const initialQueueLength = this.queue.length; + + while (this.queue.length > 0 && attempts < maxAttempts) { + const queueLengthBefore = this.queue.length; + try { + await this.flush(); + } catch (error) { + logger.error("EventQueue: Failed to flush during cleanup", error); + // Break on error to avoid infinite loop if flush keeps failing + break; + } + + // If queue length didn't decrease, flush is silently failing + if (this.queue.length >= queueLengthBefore) { + logger.warn("EventQueue: Flush did not reduce queue size, aborting cleanup"); + break; + } + + attempts++; + } + + if (attempts >= maxAttempts && this.queue.length > 0) { + logger.warn( + `EventQueue: Cleanup safety limit reached. Discarding ${this.queue.length} events.` + ); + this.queue = []; + this.payloadHashes.clear(); + } + + if (initialQueueLength > 0) { + logger.debug(`EventQueue: Cleanup completed, flushed ${initialQueueLength - this.queue.length} events`); + } + + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + if (this.appStateSubscription) { + this.appStateSubscription.remove(); + this.appStateSubscription = null; + } + } +} diff --git a/src/lib/event/index.ts b/src/lib/event/index.ts new file mode 100644 index 0000000..d592a38 --- /dev/null +++ b/src/lib/event/index.ts @@ -0,0 +1,4 @@ +export * from "./EventFactory"; +export * from "./EventManager"; +export * from "./EventQueue"; +export * from "./types"; diff --git a/src/lib/event/types.ts b/src/lib/event/types.ts new file mode 100644 index 0000000..b854115 --- /dev/null +++ b/src/lib/event/types.ts @@ -0,0 +1,104 @@ +import { + Address, + APIEvent, + IFormoEvent, + IFormoEventContext, + IFormoEventProperties, + Nullable, + SignatureStatus, + TransactionStatus, + ChainID, +} from "../../types"; + +export interface IEventFactory { + create( + event: APIEvent, + address?: Address, + userId?: string + ): Promise; + + generateScreenEvent( + name: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateDetectWalletEvent( + providerName: string, + rdns: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateIdentifyEvent( + providerName: string, + rdns: string, + address: Nullable
, + userId?: Nullable, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateConnectEvent( + chainId: ChainID, + address: Address, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateDisconnectEvent( + chainId?: ChainID, + address?: Address, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateChainChangedEvent( + chainId: ChainID, + address: Address, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateSignatureEvent( + status: SignatureStatus, + chainId: ChainID, + address: Address, + message: string, + signatureHash?: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateTransactionEvent( + status: TransactionStatus, + chainId: ChainID, + address: Address, + data: string, + to: string, + value: string, + transactionHash?: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; + + generateTrackEvent( + event: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext + ): Promise; +} + +export interface IEventManager { + addEvent(event: APIEvent, address?: Address, userId?: string): Promise; +} + +export interface IEventQueue { + enqueue( + event: IFormoEvent, + callback?: (...args: unknown[]) => void + ): Promise; + flush(callback?: (...args: unknown[]) => void): Promise; + clear(): void; + cleanup(): Promise; +} diff --git a/src/lib/logger/index.ts b/src/lib/logger/index.ts new file mode 100644 index 0000000..a91cf52 --- /dev/null +++ b/src/lib/logger/index.ts @@ -0,0 +1,56 @@ +export type LogLevel = "debug" | "info" | "warn" | "error" | "log"; + +interface LoggerConfig { + enabled?: boolean; + enabledLevels?: LogLevel[]; +} + +class LoggerClass { + private config: LoggerConfig = { + enabled: false, + enabledLevels: [], + }; + + init(config: LoggerConfig) { + this.config = config; + } + + private shouldLog(level: LogLevel): boolean { + if (!this.config.enabled) return false; + if (this.config.enabledLevels?.length === 0) return true; + return this.config.enabledLevels?.includes(level) ?? false; + } + + debug(...args: unknown[]) { + if (this.shouldLog("debug")) { + console.debug("[Formo RN]", ...args); + } + } + + info(...args: unknown[]) { + if (this.shouldLog("info")) { + console.info("[Formo RN]", ...args); + } + } + + warn(...args: unknown[]) { + if (this.shouldLog("warn")) { + console.warn("[Formo RN]", ...args); + } + } + + error(...args: unknown[]) { + if (this.shouldLog("error")) { + console.error("[Formo RN]", ...args); + } + } + + log(...args: unknown[]) { + if (this.shouldLog("log")) { + console.log("[Formo RN]", ...args); + } + } +} + +export const Logger = new LoggerClass(); +export const logger = Logger; diff --git a/src/lib/session/index.ts b/src/lib/session/index.ts new file mode 100644 index 0000000..ab97e3c --- /dev/null +++ b/src/lib/session/index.ts @@ -0,0 +1,103 @@ +import { + SESSION_WALLET_DETECTED_KEY, + SESSION_WALLET_IDENTIFIED_KEY, +} from "../../constants"; +import { storage } from "../storage"; +import { logger } from "../logger"; + +export { SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY }; + +/** + * Session manager for tracking wallet detection and identification + * Persists to session storage to avoid duplicate detection/identification events + * within the same session + */ +export class FormoAnalyticsSession { + private detectedWallets: Set = new Set(); + private identifiedWallets: Set = new Set(); + + constructor() { + this.loadFromStorage(); + } + + /** + * Load session state from storage + */ + private loadFromStorage(): void { + try { + const detected = storage().get(SESSION_WALLET_DETECTED_KEY); + if (detected) { + const parsed = JSON.parse(detected) as string[]; + this.detectedWallets = new Set(parsed); + } + + const identified = storage().get(SESSION_WALLET_IDENTIFIED_KEY); + if (identified) { + const parsed = JSON.parse(identified) as string[]; + this.identifiedWallets = new Set(parsed); + } + } catch (error) { + logger.debug("Session: Failed to load from storage", error); + } + } + + /** + * Save session state to storage + */ + private saveToStorage(): void { + try { + storage().set( + SESSION_WALLET_DETECTED_KEY, + JSON.stringify(Array.from(this.detectedWallets)) + ); + storage().set( + SESSION_WALLET_IDENTIFIED_KEY, + JSON.stringify(Array.from(this.identifiedWallets)) + ); + } catch (error) { + logger.debug("Session: Failed to save to storage", error); + } + } + + /** + * Check if a wallet has been detected in this session + */ + public isWalletDetected(rdns: string): boolean { + return this.detectedWallets.has(rdns); + } + + /** + * Mark a wallet as detected + */ + public markWalletDetected(rdns: string): void { + this.detectedWallets.add(rdns); + this.saveToStorage(); + } + + /** + * Check if a wallet + address combination has been identified + */ + public isWalletIdentified(address: string, rdns: string): boolean { + const key = `${address.toLowerCase()}:${rdns}`; + return this.identifiedWallets.has(key); + } + + /** + * Mark a wallet + address combination as identified + */ + public markWalletIdentified(address: string, rdns: string): void { + const key = `${address.toLowerCase()}:${rdns}`; + this.identifiedWallets.add(key); + this.saveToStorage(); + } + + /** + * Clear all session data + */ + public clear(): void { + this.detectedWallets.clear(); + this.identifiedWallets.clear(); + storage().remove(SESSION_WALLET_DETECTED_KEY); + storage().remove(SESSION_WALLET_IDENTIFIED_KEY); + } +} diff --git a/src/lib/storage/AsyncStorageAdapter.ts b/src/lib/storage/AsyncStorageAdapter.ts new file mode 100644 index 0000000..4eec6b7 --- /dev/null +++ b/src/lib/storage/AsyncStorageAdapter.ts @@ -0,0 +1,173 @@ +import StorageBlueprint from "./StorageBlueprint"; +import { AsyncStorageInterface } from "./types"; +import { logger } from "../logger"; + +/** + * AsyncStorage adapter for React Native + * Provides persistent storage across app restarts + */ +class AsyncStorageAdapter extends StorageBlueprint { + private asyncStorage: AsyncStorageInterface | null = null; + private cache: Map = new Map(); + + constructor(writeKey: string, asyncStorage?: AsyncStorageInterface) { + super(writeKey); + if (asyncStorage) { + this.asyncStorage = asyncStorage; + } + } + + /** + * Initialize with AsyncStorage instance and preload all Formo keys + * This ensures consent flags and other critical data are available synchronously + */ + public async initialize(asyncStorage: AsyncStorageInterface): Promise { + this.asyncStorage = asyncStorage; + + // Preload all Formo keys into cache for synchronous access + // This is critical for consent checks on cold start (GDPR compliance) + try { + const allKeys = await asyncStorage.getAllKeys(); + // getKey("") returns "formo_rn_{writeKey}_" - use this exact prefix + // to avoid matching keys from other instances (e.g., "abc" matching "abc123") + const formoPrefix = this.getKey(""); + + // Filter to only our keys (exact prefix match including trailing underscore) + const formoKeys = allKeys.filter((key) => key.startsWith(formoPrefix)); + + if (formoKeys.length > 0) { + const pairs = await asyncStorage.multiGet(formoKeys); + for (const [key, value] of pairs) { + if (value !== null) { + this.cache.set(key, value); + } + } + logger.debug( + `AsyncStorageAdapter: Preloaded ${formoKeys.length} keys into cache` + ); + } + } catch (error) { + logger.error("AsyncStorageAdapter: Failed to preload keys", error); + } + + logger.debug("AsyncStorageAdapter: Initialized"); + } + + public isAvailable(): boolean { + return this.asyncStorage !== null; + } + + /** + * Synchronous get from cache (may return stale data) + * Use getAsync for guaranteed fresh data + */ + public get(key: string): string | null { + const cachedValue = this.cache.get(this.getKey(key)); + if (cachedValue !== undefined) { + return cachedValue; + } + + // Trigger async fetch to populate cache + this.getAsync(key).catch(() => { + // Ignore errors in background fetch + }); + + return null; + } + + /** + * Async get from storage + */ + public async getAsync(key: string): Promise { + if (!this.asyncStorage) { + return this.cache.get(this.getKey(key)) ?? null; + } + + try { + const fullKey = this.getKey(key); + const value = await this.asyncStorage.getItem(fullKey); + + if (value !== null) { + this.cache.set(fullKey, value); + } + + return value; + } catch (error) { + logger.error("AsyncStorageAdapter: Failed to get item", error); + return this.cache.get(this.getKey(key)) ?? null; + } + } + + /** + * Synchronous set (writes to cache immediately, persists async) + */ + public set(key: string, value: string): void { + const fullKey = this.getKey(key); + this.cache.set(fullKey, value); + + // Persist asynchronously + this.setAsync(key, value).catch((error) => { + logger.error("AsyncStorageAdapter: Failed to persist item", error); + }); + } + + /** + * Async set to storage + */ + public async setAsync(key: string, value: string): Promise { + const fullKey = this.getKey(key); + this.cache.set(fullKey, value); + + if (!this.asyncStorage) { + return; + } + + try { + await this.asyncStorage.setItem(fullKey, value); + } catch (error) { + logger.error("AsyncStorageAdapter: Failed to set item", error); + throw error; + } + } + + /** + * Synchronous remove (removes from cache immediately, persists async) + */ + public remove(key: string): void { + const fullKey = this.getKey(key); + this.cache.delete(fullKey); + + // Persist asynchronously + this.removeAsync(key).catch((error) => { + logger.error("AsyncStorageAdapter: Failed to remove item", error); + }); + } + + /** + * Async remove from storage + */ + public async removeAsync(key: string): Promise { + const fullKey = this.getKey(key); + this.cache.delete(fullKey); + + if (!this.asyncStorage) { + return; + } + + try { + await this.asyncStorage.removeItem(fullKey); + } catch (error) { + logger.error("AsyncStorageAdapter: Failed to remove item", error); + throw error; + } + } + + /** + * Clear all cached data + */ + public clearCache(): void { + this.cache.clear(); + } +} + +export default AsyncStorageAdapter; diff --git a/src/lib/storage/MemoryStorage.ts b/src/lib/storage/MemoryStorage.ts new file mode 100644 index 0000000..5aac1b2 --- /dev/null +++ b/src/lib/storage/MemoryStorage.ts @@ -0,0 +1,43 @@ +import StorageBlueprint from "./StorageBlueprint"; + +/** + * In-memory storage fallback + * Data is lost when the app is closed + */ +class MemoryStorage extends StorageBlueprint { + private storage: Map = new Map(); + + public isAvailable(): boolean { + return true; + } + + public get(key: string): string | null { + return this.storage.get(this.getKey(key)) ?? null; + } + + public async getAsync(key: string): Promise { + return this.get(key); + } + + public set(key: string, value: string): void { + this.storage.set(this.getKey(key), value); + } + + public async setAsync(key: string, value: string): Promise { + this.set(key, value); + } + + public remove(key: string): void { + this.storage.delete(this.getKey(key)); + } + + public async removeAsync(key: string): Promise { + this.remove(key); + } + + public clear(): void { + this.storage.clear(); + } +} + +export default MemoryStorage; diff --git a/src/lib/storage/StorageBlueprint.ts b/src/lib/storage/StorageBlueprint.ts new file mode 100644 index 0000000..59d4ba5 --- /dev/null +++ b/src/lib/storage/StorageBlueprint.ts @@ -0,0 +1,30 @@ +import { STORAGE_PREFIX } from "../../constants"; +import { IStorage } from "./types"; + +/** + * Base storage class with key prefixing + */ +abstract class StorageBlueprint implements IStorage { + protected writeKey: string; + + constructor(writeKey: string) { + this.writeKey = writeKey; + } + + /** + * Generate storage key with prefix + */ + public getKey(key: string): string { + return `${STORAGE_PREFIX}${this.writeKey}_${key}`; + } + + abstract isAvailable(): boolean; + abstract get(key: string): string | null; + abstract getAsync(key: string): Promise; + abstract set(key: string, value: string): void; + abstract setAsync(key: string, value: string): Promise; + abstract remove(key: string): void; + abstract removeAsync(key: string): Promise; +} + +export default StorageBlueprint; diff --git a/src/lib/storage/StorageManager.ts b/src/lib/storage/StorageManager.ts new file mode 100644 index 0000000..c924bed --- /dev/null +++ b/src/lib/storage/StorageManager.ts @@ -0,0 +1,112 @@ +import { logger } from "../logger"; +import AsyncStorageAdapter from "./AsyncStorageAdapter"; +import MemoryStorage from "./MemoryStorage"; +import { IStorage, StorageType, AsyncStorageInterface } from "./types"; + +/** + * Storage manager for React Native SDK + * Uses AsyncStorage as primary storage with MemoryStorage fallback + */ +export class StorageManager { + private storages: Map = new Map(); + + constructor(private readonly writeKey: string) {} + + /** + * Initialize with AsyncStorage instance + * This should be called during SDK initialization + */ + public async initialize(asyncStorage: AsyncStorageInterface): Promise { + // Create and initialize the AsyncStorage adapter directly. + // We bypass getStorage() here because it checks isAvailable() which + // returns false on an uninitialized adapter and would fall back to + // MemoryStorage, causing a crash when we call adapter.initialize(). + const adapter = new AsyncStorageAdapter(this.writeKey); + await adapter.initialize(asyncStorage); + this.storages.set("asyncStorage", adapter); + + logger.debug("StorageManager: Initialized with AsyncStorage"); + } + + /** + * Get storage instance by type + */ + public getStorage(type: StorageType): IStorage { + if (!this.storages.has(type)) { + const storage = this.createStorage(type); + + // If storage is not available, fallback to memory and cache the fallback + if (!storage.isAvailable() && type !== "memoryStorage") { + logger.warn( + `Storage ${type} is not available, falling back to memoryStorage` + ); + const fallback = this.getStorage("memoryStorage"); + this.storages.set(type, fallback); + return fallback; + } + + this.storages.set(type, storage); + } + + return this.storages.get(type)!; + } + + /** + * Create storage instance + */ + private createStorage(type: StorageType): IStorage { + switch (type) { + case "asyncStorage": + return new AsyncStorageAdapter(this.writeKey); + + case "memoryStorage": + default: + return new MemoryStorage(this.writeKey); + } + } + + /** + * Get primary storage (AsyncStorage with fallback) + */ + public getPrimaryStorage(): IStorage { + const asyncStorage = this.getStorage("asyncStorage"); + if (asyncStorage.isAvailable()) { + return asyncStorage; + } + return this.getStorage("memoryStorage"); + } +} + +// Global storage manager instance +let storageManagerInstance: StorageManager | null = null; + +/** + * Initialize global storage manager + */ +export function initStorageManager(writeKey: string): StorageManager { + if (!storageManagerInstance || storageManagerInstance["writeKey"] !== writeKey) { + // Clean up old instance before creating new one + if (storageManagerInstance) { + logger.debug("StorageManager: Replacing instance with new writeKey"); + } + storageManagerInstance = new StorageManager(writeKey); + } + return storageManagerInstance; +} + +/** + * Get global storage manager instance + */ +export function getStorageManager(): StorageManager | null { + return storageManagerInstance; +} + +/** + * Get primary storage + */ +export function storage(): IStorage { + if (!storageManagerInstance) { + throw new Error("StorageManager not initialized. Call initStorageManager first."); + } + return storageManagerInstance.getPrimaryStorage(); +} diff --git a/src/lib/storage/index.ts b/src/lib/storage/index.ts new file mode 100644 index 0000000..a21299e --- /dev/null +++ b/src/lib/storage/index.ts @@ -0,0 +1,4 @@ +export * from "./StorageManager"; +export * from "./types"; +export { default as AsyncStorageAdapter } from "./AsyncStorageAdapter"; +export { default as MemoryStorage } from "./MemoryStorage"; diff --git a/src/lib/storage/types.ts b/src/lib/storage/types.ts new file mode 100644 index 0000000..a7fadf0 --- /dev/null +++ b/src/lib/storage/types.ts @@ -0,0 +1,23 @@ +export type StorageType = "asyncStorage" | "memoryStorage"; + +export interface IStorage { + isAvailable(): boolean; + get(key: string): string | null; + getAsync(key: string): Promise; + set(key: string, value: string): void; + setAsync(key: string, value: string): Promise; + remove(key: string): void; + removeAsync(key: string): Promise; + getKey(key: string): string; +} + +export interface AsyncStorageInterface { + getItem(key: string): Promise; + setItem(key: string, value: string): Promise; + removeItem(key: string): Promise; + getAllKeys(): Promise; + multiGet(keys: readonly string[]): Promise; + multiSet?(keyValuePairs: [string, string][]): Promise; + multiRemove(keys: readonly string[]): Promise; + clear?(): Promise; +} diff --git a/src/lib/wagmi/WagmiEventHandler.ts b/src/lib/wagmi/WagmiEventHandler.ts new file mode 100644 index 0000000..5ce8f41 --- /dev/null +++ b/src/lib/wagmi/WagmiEventHandler.ts @@ -0,0 +1,574 @@ +/** + * WagmiEventHandler for React Native + * + * Handles wallet event tracking by hooking into Wagmi v2's config.subscribe() + * and TanStack Query's MutationCache. + */ + +import { SignatureStatus, TransactionStatus } from "../../types/events"; +import { logger } from "../logger"; +import { + WagmiConfig, + WagmiState, + QueryClient, + MutationCacheEvent, + UnsubscribeFn, + WagmiTrackingState, + WagmiMutationKey, +} from "./types"; + +// Interface for FormoAnalytics to avoid circular dependency +interface IFormoAnalyticsInstance { + connect( + params: { chainId: number; address: string }, + properties?: Record + ): Promise; + disconnect(params?: { + chainId?: number; + address?: string; + }): Promise; + chain(params: { chainId: number; address?: string }): Promise; + signature(params: { + status: SignatureStatus; + chainId: number; + address: string; + message: string; + signatureHash?: string; + }): Promise; + transaction(params: { + status: TransactionStatus; + chainId: number; + address: string; + data?: string; + to?: string; + value?: string; + transactionHash?: string; + }): Promise; + isAutocaptureEnabled( + eventType: "connect" | "disconnect" | "signature" | "transaction" | "chain" + ): boolean; +} + +export class WagmiEventHandler { + private formo: IFormoAnalyticsInstance; + private wagmiConfig: WagmiConfig; + private queryClient?: QueryClient; + private unsubscribers: UnsubscribeFn[] = []; + private trackingState: WagmiTrackingState = { + isProcessing: false, + }; + private processedMutations = new Set(); + private pendingStatusChanges: Array<{ + status: WagmiState["status"]; + prevStatus: WagmiState["status"]; + }> = []; + + constructor( + formoAnalytics: IFormoAnalyticsInstance, + wagmiConfig: WagmiConfig, + queryClient?: QueryClient + ) { + this.formo = formoAnalytics; + this.wagmiConfig = wagmiConfig; + this.queryClient = queryClient; + + logger.info("WagmiEventHandler: Initializing Wagmi integration"); + + this.setupConnectionListeners(); + + if (this.queryClient) { + this.setupMutationTracking(); + } else { + logger.warn( + "WagmiEventHandler: QueryClient not provided, signature and transaction events will not be tracked" + ); + } + } + + /** + * Set up listeners for wallet connection, disconnection, and chain changes + */ + private setupConnectionListeners(): void { + logger.info("WagmiEventHandler: Setting up connection listeners"); + + // Subscribe to status changes + const statusUnsubscribe = this.wagmiConfig.subscribe( + (state: WagmiState) => state.status, + (status, prevStatus) => { + this.handleStatusChange(status, prevStatus); + } + ); + this.unsubscribers.push(statusUnsubscribe); + + // Subscribe to chain ID changes + const chainIdUnsubscribe = this.wagmiConfig.subscribe( + (state: WagmiState) => state.chainId, + (chainId, prevChainId) => { + this.handleChainChange(chainId, prevChainId); + } + ); + this.unsubscribers.push(chainIdUnsubscribe); + + logger.info("WagmiEventHandler: Connection listeners set up successfully"); + } + + // Maximum pending status changes to prevent unbounded queue growth + private static readonly MAX_PENDING_STATUS_CHANGES = 10; + + /** + * Handle status changes (connect/disconnect) + */ + private async handleStatusChange( + status: WagmiState["status"], + prevStatus: WagmiState["status"] + ): Promise { + if (this.trackingState.isProcessing) { + // Limit queue size to prevent unbounded growth during rapid status changes + if (this.pendingStatusChanges.length >= WagmiEventHandler.MAX_PENDING_STATUS_CHANGES) { + logger.warn("WagmiEventHandler: Pending status change queue full, dropping oldest"); + this.pendingStatusChanges.shift(); + } + // Queue status change to process after current one completes + this.pendingStatusChanges.push({ status, prevStatus }); + logger.debug( + "WagmiEventHandler: Queuing status change for later processing" + ); + return; + } + + this.trackingState.isProcessing = true; + + try { + // Process current status change + await this.processStatusChange(status, prevStatus); + + // Process pending status changes iteratively (inline, no recursion) + while (this.pendingStatusChanges.length > 0) { + const pending = this.pendingStatusChanges.shift()!; + await this.processStatusChange(pending.status, pending.prevStatus); + } + } finally { + this.trackingState.isProcessing = false; + } + } + + /** + * Process a single status change (extracted for iterative processing) + */ + private async processStatusChange( + status: WagmiState["status"], + prevStatus: WagmiState["status"] + ): Promise { + try { + const state = this.getState(); + const address = this.getConnectedAddress(state); + const chainId = state.chainId; + + logger.info("WagmiEventHandler: Status changed", { + status, + prevStatus, + address, + chainId, + }); + + // Handle disconnect + if (status === "disconnected" && prevStatus === "connected") { + if (this.formo.isAutocaptureEnabled("disconnect")) { + await this.formo.disconnect({ + chainId: this.trackingState.lastChainId, + address: this.trackingState.lastAddress, + }); + } + this.trackingState.lastAddress = undefined; + this.trackingState.lastChainId = undefined; + } + + // Handle connect + if (status === "connected" && prevStatus !== "connected") { + if (address && chainId !== undefined) { + this.trackingState.lastAddress = address; + this.trackingState.lastChainId = chainId; + + if (this.formo.isAutocaptureEnabled("connect")) { + const connectorName = this.getConnectorName(state); + const connectorId = this.getConnectorId(state); + await this.formo.connect( + { chainId, address }, + { + ...(connectorName && { providerName: connectorName }), + // Connector ID is typically the rdns for EIP-6963 wallets + ...(connectorId && { rdns: connectorId }), + } + ); + } + } + } + + this.trackingState.lastStatus = status; + } catch (error) { + logger.error("WagmiEventHandler: Error handling status change:", error); + } + } + + /** + * Handle chain ID changes + */ + private async handleChainChange( + chainId: number | undefined, + prevChainId: number | undefined + ): Promise { + // Skip if no change, chainId is undefined, or this is initial connection (prevChainId undefined) + if (chainId === prevChainId || chainId === undefined || prevChainId === undefined) { + return; + } + + const state = this.getState(); + if (state.status !== "connected") { + return; + } + + const address = this.getConnectedAddress(state); + if (!address) { + logger.warn("WagmiEventHandler: Chain changed but no address found"); + return; + } + + logger.info("WagmiEventHandler: Chain changed", { + chainId, + prevChainId, + address, + }); + + this.trackingState.lastChainId = chainId; + + if (this.formo.isAutocaptureEnabled("chain")) { + try { + await this.formo.chain({ chainId, address }); + } catch (error) { + logger.error("WagmiEventHandler: Error tracking chain change:", error); + } + } + } + + /** + * Set up mutation tracking for signatures and transactions + */ + private setupMutationTracking(): void { + if (!this.queryClient) { + return; + } + + logger.info("WagmiEventHandler: Setting up mutation tracking"); + + const mutationCache = this.queryClient.getMutationCache(); + const unsubscribe = mutationCache.subscribe((event: MutationCacheEvent) => { + this.handleMutationEvent(event); + }); + + this.unsubscribers.push(unsubscribe); + logger.info("WagmiEventHandler: Mutation tracking set up successfully"); + } + + /** + * Handle mutation cache events + */ + private handleMutationEvent(event: MutationCacheEvent): void { + if (event.type !== "updated") { + return; + } + + const mutation = event.mutation; + const mutationKey = mutation.options.mutationKey; + + if (!mutationKey || mutationKey.length === 0) { + return; + } + + const mutationType = mutationKey[0] as string; + const state = mutation.state; + + const mutationStateKey = `${mutation.mutationId}:${state.status}`; + + if (this.processedMutations.has(mutationStateKey)) { + return; + } + + this.processedMutations.add(mutationStateKey); + + logger.debug("WagmiEventHandler: Mutation event", { + mutationType, + mutationId: mutation.mutationId, + status: state.status, + }); + + if (mutationType === "signMessage" || mutationType === "signTypedData") { + this.handleSignatureMutation( + mutationType as WagmiMutationKey, + mutation + ); + } + + if ( + mutationType === "sendTransaction" || + mutationType === "writeContract" + ) { + this.handleTransactionMutation( + mutationType as WagmiMutationKey, + mutation + ); + } + + // Cleanup old mutations + if (this.processedMutations.size > 1000) { + const entries = Array.from(this.processedMutations); + for (let i = 0; i < 500; i++) { + const entry = entries[i]; + if (entry) { + this.processedMutations.delete(entry); + } + } + } + } + + /** + * Handle signature mutations + */ + private handleSignatureMutation( + mutationType: WagmiMutationKey, + mutation: MutationCacheEvent["mutation"] + ): void { + if (!this.formo.isAutocaptureEnabled("signature")) { + return; + } + + const state = mutation.state; + const variables = state.variables || {}; + const chainId = this.trackingState.lastChainId; + const address = this.trackingState.lastAddress; + + if (!address) { + logger.warn( + "WagmiEventHandler: Signature event but no address available" + ); + return; + } + + if (!chainId || chainId === 0) { + logger.warn( + "WagmiEventHandler: Signature event but no valid chainId available" + ); + return; + } + + try { + let status: SignatureStatus; + let signatureHash: string | undefined; + + if (state.status === "pending") { + status = SignatureStatus.REQUESTED; + } else if (state.status === "success") { + status = SignatureStatus.CONFIRMED; + signatureHash = state.data as string; + } else if (state.status === "error") { + status = SignatureStatus.REJECTED; + } else { + return; + } + + let message: string; + if (mutationType === "signMessage") { + message = (variables.message as string) || ""; + } else { + message = JSON.stringify(variables.message || variables.types || {}); + } + + logger.info("WagmiEventHandler: Tracking signature event", { + status, + mutationType, + address, + chainId, + }); + + this.formo.signature({ + status, + chainId, + address, + message, + ...(signatureHash && { signatureHash }), + }).catch((error) => { + logger.error("WagmiEventHandler: Error tracking signature:", error); + }); + } catch (error) { + logger.error( + "WagmiEventHandler: Error handling signature mutation:", + error + ); + } + } + + /** + * Handle transaction mutations + */ + private handleTransactionMutation( + mutationType: WagmiMutationKey, + mutation: MutationCacheEvent["mutation"] + ): void { + if (!this.formo.isAutocaptureEnabled("transaction")) { + return; + } + + const state = mutation.state; + const variables = state.variables || {}; + const chainId = + this.trackingState.lastChainId || + (variables.chainId as number | undefined); + // Only use variables.account as fallback, not variables.address which is the contract address + const address = + this.trackingState.lastAddress || + (variables.account as string | undefined); + + if (!address) { + logger.warn( + "WagmiEventHandler: Transaction event but no address available" + ); + return; + } + + if (!chainId || chainId === 0) { + logger.warn( + "WagmiEventHandler: Transaction event but no valid chainId available" + ); + return; + } + + try { + let status: TransactionStatus; + let transactionHash: string | undefined; + + if (state.status === "pending") { + status = TransactionStatus.STARTED; + } else if (state.status === "success") { + status = TransactionStatus.BROADCASTED; + transactionHash = state.data as string; + } else if (state.status === "error") { + status = TransactionStatus.REJECTED; + } else { + return; + } + + const data = variables.data as string | undefined; + const to = + (variables.to as string | undefined) || + (variables.address as string | undefined); + const value = variables.value?.toString(); + + logger.info("WagmiEventHandler: Tracking transaction event", { + status, + mutationType, + address, + chainId, + transactionHash, + }); + + this.formo.transaction({ + status, + chainId, + address, + ...(data && { data }), + ...(to && { to }), + ...(value && { value }), + ...(transactionHash && { transactionHash }), + }).catch((error) => { + logger.error("WagmiEventHandler: Error tracking transaction:", error); + }); + } catch (error) { + logger.error( + "WagmiEventHandler: Error handling transaction mutation:", + error + ); + } + } + + /** + * Get current Wagmi state + */ + private getState(): WagmiState { + if (typeof this.wagmiConfig.getState === "function") { + return this.wagmiConfig.getState(); + } + + if (this.wagmiConfig.state) { + return this.wagmiConfig.state; + } + + logger.warn( + "WagmiEventHandler: Unable to get state from config, returning default state" + ); + return { + status: "disconnected", + connections: new Map(), + current: undefined, + chainId: undefined, + }; + } + + /** + * Get connected address from state + */ + private getConnectedAddress(state: WagmiState): string | undefined { + if (!state.current) { + return undefined; + } + + const connection = state.connections.get(state.current); + if (!connection || connection.accounts.length === 0) { + return undefined; + } + + return connection.accounts[0]; + } + + /** + * Get connector name from state + */ + private getConnectorName(state: WagmiState): string | undefined { + if (!state.current) { + return undefined; + } + + const connection = state.connections.get(state.current); + return connection?.connector.name; + } + + /** + * Get connector ID from state (typically the rdns for EIP-6963 wallets) + */ + private getConnectorId(state: WagmiState): string | undefined { + if (!state.current) { + return undefined; + } + + const connection = state.connections.get(state.current); + return connection?.connector.id; + } + + /** + * Clean up subscriptions + */ + public cleanup(): void { + logger.info("WagmiEventHandler: Cleaning up subscriptions"); + + for (const unsubscribe of this.unsubscribers) { + try { + unsubscribe(); + } catch (error) { + logger.error("WagmiEventHandler: Error during cleanup:", error); + } + } + + this.unsubscribers = []; + this.processedMutations.clear(); + this.pendingStatusChanges = []; + logger.info("WagmiEventHandler: Cleanup complete"); + } +} diff --git a/src/lib/wagmi/index.ts b/src/lib/wagmi/index.ts new file mode 100644 index 0000000..c2c1d2a --- /dev/null +++ b/src/lib/wagmi/index.ts @@ -0,0 +1,2 @@ +export * from "./WagmiEventHandler"; +export * from "./types"; diff --git a/src/lib/wagmi/types.ts b/src/lib/wagmi/types.ts new file mode 100644 index 0000000..4d4eb3d --- /dev/null +++ b/src/lib/wagmi/types.ts @@ -0,0 +1,71 @@ +/** + * Wagmi types for React Native SDK + */ + +export interface WagmiState { + status: "connecting" | "connected" | "disconnected" | "reconnecting"; + connections: Map< + string, + { + accounts: readonly string[]; + chainId: number; + connector: { + name: string; + /** Connector ID - for EIP-6963 injected wallets, this is typically the rdns */ + id: string; + type?: string; + icon?: string; + }; + } + >; + current?: string; + chainId?: number; +} + +export interface WagmiConfig { + subscribe: ( + selector: (state: WagmiState) => T, + listener: (selectedState: T, prevSelectedState: T) => void + ) => () => void; + getState?: () => WagmiState; + state?: WagmiState; +} + +export interface QueryClient { + getMutationCache: () => MutationCache; +} + +export interface MutationCache { + subscribe: (callback: (event: MutationCacheEvent) => void) => () => void; +} + +export interface MutationCacheEvent { + type: "added" | "updated" | "removed"; + mutation: { + mutationId: number; + options: { + mutationKey?: readonly string[]; + }; + state: { + status: "idle" | "pending" | "success" | "error"; + data?: unknown; + error?: unknown; + variables?: Record; + }; + }; +} + +export type UnsubscribeFn = () => void; + +export interface WagmiTrackingState { + isProcessing: boolean; + lastAddress?: string; + lastChainId?: number; + lastStatus?: WagmiState["status"]; +} + +export type WagmiMutationKey = + | "signMessage" + | "signTypedData" + | "sendTransaction" + | "writeContract"; diff --git a/src/types/base.ts b/src/types/base.ts new file mode 100644 index 0000000..4f039de --- /dev/null +++ b/src/types/base.ts @@ -0,0 +1,237 @@ +import { ReactNode } from "react"; +import { LogLevel } from "../lib/logger"; +import { + IFormoEventContext, + IFormoEventProperties, + SignatureStatus, + TransactionStatus, +} from "./events"; + +export type Nullable = T | null; +// Decimal chain ID +export type ChainID = number; + +// Address (EVM, Solana, etc.) +export type Address = string; + +export type ValidInputTypes = Uint8Array | bigint | string | number | boolean; + +export interface IFormoAnalytics { + screen( + name: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + reset(): void; + cleanup(): Promise; + detect( + params: { rdns: string; providerName: string }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + connect( + params: { chainId: ChainID; address: Address }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + disconnect( + params?: { chainId?: ChainID; address?: Address }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + chain( + params: { chainId: ChainID; address?: Address }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + signature( + params: { + status: SignatureStatus; + chainId: ChainID; + address: Address; + message: string; + signatureHash?: string; + }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + transaction( + params: { + status: TransactionStatus; + chainId: ChainID; + address: Address; + data?: string; + to?: string; + value?: string; + transactionHash?: string; + }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + identify( + params: { + address: Address; + providerName?: string; + userId?: string; + rdns?: string; + }, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + track( + event: string, + properties?: IFormoEventProperties, + context?: IFormoEventContext, + callback?: (...args: unknown[]) => void + ): Promise; + + // Traffic source management + setTrafficSourceFromUrl(url: string): void; + + // Consent management methods + optOutTracking(): void; + optInTracking(): void; + hasOptedOutTracking(): boolean; +} + +export interface Config { + writeKey: string; +} + +/** + * Configuration options for controlling tracking exclusions + */ +export interface TrackingOptions { + excludeChains?: ChainID[]; +} + +/** + * Configuration options for controlling wallet event autocapture + * All events are enabled by default unless explicitly set to false + */ +export interface AutocaptureOptions { + /** + * Track wallet connect events + * @default true + */ + connect?: boolean; + + /** + * Track wallet disconnect events + * @default true + */ + disconnect?: boolean; + + /** + * Track wallet signature events (personal_sign, eth_signTypedData_v4) + * @default true + */ + signature?: boolean; + + /** + * Track wallet transaction events (eth_sendTransaction) + * @default true + */ + transaction?: boolean; + + /** + * Track wallet chain change events + * @default true + */ + chain?: boolean; +} + +/** + * Configuration options for Wagmi integration + * Allows the SDK to hook into Wagmi v2 wallet events + */ +export interface WagmiOptions { + /** + * Wagmi config instance from createConfig() + * The SDK will subscribe to this config's state changes to track wallet events + */ + config: any; + + /** + * Optional QueryClient instance from @tanstack/react-query + * Required for tracking signature and transaction events via mutation cache + * If not provided, only connection/disconnection/chain events will be tracked + */ + queryClient?: any; +} + +/** + * App information for context enrichment + */ +export interface AppInfo { + /** + * App name + */ + name?: string; + + /** + * App version + */ + version?: string; + + /** + * App build number + */ + build?: string; + + /** + * Bundle/package identifier + */ + bundleId?: string; +} + +export interface Options { + tracking?: boolean | TrackingOptions; + /** + * Control wallet event autocapture + * - `false`: Disable all wallet autocapture + * - `true`: Enable all wallet events (default) + * - `AutocaptureOptions`: Granular control over specific events + * @default true + */ + autocapture?: boolean | AutocaptureOptions; + /** + * Wagmi integration configuration + * When provided, the SDK will hook into Wagmi's event system + * @requires wagmi@>=2.0.0 + * @requires @tanstack/react-query@>=5.0.0 (for mutation tracking) + */ + wagmi?: WagmiOptions; + /** + * Custom API host for sending events + */ + apiHost?: string; + flushAt?: number; + flushInterval?: number; + retryCount?: number; + maxQueueSize?: number; + logger?: { + enabled?: boolean; + levels?: LogLevel[]; + }; + /** + * App information for context enrichment + */ + app?: AppInfo; + ready?: (formo: IFormoAnalytics) => void; +} + +export interface FormoAnalyticsProviderProps { + writeKey: string; + options?: Options; + disabled?: boolean; + children: ReactNode; +} diff --git a/src/types/events.ts b/src/types/events.ts new file mode 100644 index 0000000..8fa5c12 --- /dev/null +++ b/src/types/events.ts @@ -0,0 +1,137 @@ +import { Address, ChainID, Nullable } from "./base"; +import { TEventChannel, TEventType } from "../constants"; + +export type AnonymousID = string; + +export interface ICommonProperties { + anonymous_id: AnonymousID; + user_id: Nullable; + address: Nullable; + type: TEventType; + channel: TEventChannel; + version: string; + original_timestamp: string; + event?: Nullable; +} + +export type IFormoEventProperties = Record; +export type IFormoEventContext = Record; + +export type UTMParameters = { + utm_source: string; + utm_medium: string; + utm_campaign: string; + utm_term: string; + utm_content: string; +}; + +export type ITrafficSource = UTMParameters & { + ref: string; + referrer: string; +}; + +export interface IFormoEvent extends ICommonProperties { + context: Nullable; + properties: Nullable; +} + +export type IFormoEventPayload = IFormoEvent & { + message_id: string; +}; + +//#region Specific Event Types +export interface ScreenAPIEvent { + type: "screen"; + name: string; +} + +export interface DetectAPIEvent { + type: "detect"; + providerName: string; + rdns: string; +} + +export interface IdentifyAPIEvent { + type: "identify"; + address: string; + providerName: string; + rdns: string; + userId?: Nullable; +} + +export interface ChainAPIEvent { + type: "chain"; + chainId: ChainID; + address: Address; +} + +export interface TransactionAPIEvent { + type: "transaction"; + status: TransactionStatus; + chainId: ChainID; + address: Address; + data: string; + to: string; + value: string; + transactionHash?: string; +} + +export interface SignatureAPIEvent { + type: "signature"; + status: SignatureStatus; + chainId: ChainID; + address: Address; + message: string; + signatureHash?: string; +} + +export interface ConnectAPIEvent { + type: "connect"; + chainId: ChainID; + address: Address; +} + +export interface DisconnectAPIEvent { + type: "disconnect"; + chainId?: ChainID; + address?: Address; +} + +export interface TrackAPIEvent { + type: "track"; + event: string; + volume?: number; + revenue?: number; + currency?: string; + points?: number; +} + +export type APIEvent = { + properties?: IFormoEventProperties; + context?: IFormoEventContext; + callback?: (...args: unknown[]) => void; +} & ( + | ScreenAPIEvent + | DetectAPIEvent + | IdentifyAPIEvent + | ChainAPIEvent + | TransactionAPIEvent + | SignatureAPIEvent + | ConnectAPIEvent + | DisconnectAPIEvent + | TrackAPIEvent +); + +export enum SignatureStatus { + REQUESTED = "requested", + REJECTED = "rejected", + CONFIRMED = "confirmed", +} + +export enum TransactionStatus { + STARTED = "started", + REJECTED = "rejected", + BROADCASTED = "broadcasted", + CONFIRMED = "confirmed", + REVERTED = "reverted", +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..ba257e3 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export * from "./base"; +export * from "./events"; diff --git a/src/utils/address.ts b/src/utils/address.ts new file mode 100644 index 0000000..42c5219 --- /dev/null +++ b/src/utils/address.ts @@ -0,0 +1,84 @@ +/** + * Address validation and checksum utilities + * + * Uses ethereum-cryptography for proper EIP-55 checksum computation + */ + +import { keccak256 } from "ethereum-cryptography/keccak.js"; +import { utf8ToBytes } from "ethereum-cryptography/utils.js"; + +/** + * Convert Uint8Array to hex string + */ +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +/** + * Check if a string is a valid Ethereum address + */ +export function isValidAddress(address: string): boolean { + if (!address) return false; + if (typeof address !== "string") return false; + + // Check if it matches basic hex address format + return /^0x[a-fA-F0-9]{40}$/.test(address); +} + +/** + * Convert address to EIP-55 checksum format + * + * Uses keccak256 from ethereum-cryptography for correct checksumming + * See: https://eips.ethereum.org/EIPS/eip-55 + */ +export function toChecksumAddress(address: string): string { + if (!isValidAddress(address)) { + return address; + } + + const lowercaseAddress = address.toLowerCase().replace("0x", ""); + const hash = toHex(keccak256(utf8ToBytes(lowercaseAddress))); + + let checksumAddress = "0x"; + + for (let i = 0; i < lowercaseAddress.length; i++) { + const char = lowercaseAddress[i]; + if (char && parseInt(hash[i] || "0", 16) >= 8) { + checksumAddress += char.toUpperCase(); + } else { + checksumAddress += char; + } + } + + return checksumAddress; +} + +/** + * Get valid address or null + */ +export function getValidAddress( + address: string | undefined | null +): string | null { + if (!address) return null; + const trimmed = typeof address === "string" ? address.trim() : address; + if (!isValidAddress(trimmed)) return null; + return trimmed; +} + +/** + * Blocked addresses that should not emit events + * (zero address, dead address) + */ +const BLOCKED_ADDRESSES = new Set([ + "0x0000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000dead", +]); + +/** + * Check if address is in blocked list + */ +export function isBlockedAddress(address: string): boolean { + return BLOCKED_ADDRESSES.has(address.toLowerCase()); +} diff --git a/src/utils/hash.ts b/src/utils/hash.ts new file mode 100644 index 0000000..5d807f5 --- /dev/null +++ b/src/utils/hash.ts @@ -0,0 +1,23 @@ +import { sha256 } from "ethereum-cryptography/sha256"; +import { utf8ToBytes, bytesToHex } from "ethereum-cryptography/utils"; + +/** + * Generate a SHA-256 hash for event deduplication + * Returns full 64 hex chars to match web SDK format + */ +export async function hash(input: string): Promise { + const bytes = utf8ToBytes(input); + const hashBytes = sha256(bytes); + return bytesToHex(hashBytes); +} + +/** + * Generate a UUID v4 + */ +export function generateUUID(): string { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 0000000..916be7c --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,139 @@ +/** + * Clamp a number between min and max values + */ +export function clampNumber(value: number, max: number, min: number): number { + return Math.min(Math.max(value, min), max); +} + +/** + * Check if value is undefined + */ +export function isUndefined(value: unknown): value is undefined { + return value === undefined; +} + +/** + * Convert a camelCase/PascalCase string to snake_case + * Handles consecutive uppercase letters (acronyms) correctly: + * - "userID" -> "user_id" + * - "XMLParser" -> "xml_parser" + * - "getHTTPResponse" -> "get_http_response" + */ +function camelToSnake(str: string): string { + return str + // Insert underscore before sequences of uppercase followed by lowercase (e.g., "XMLParser" -> "XML_Parser") + .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2") + // Insert underscore before single uppercase preceded by lowercase (e.g., "userID" -> "user_ID") + .replace(/([a-z\d])([A-Z])/g, "$1_$2") + .toLowerCase(); +} + +/** + * Check if value is a plain object (not Date, Map, Set, RegExp, etc.) + */ +function isPlainObject(value: unknown): value is Record { + if (value === null || typeof value !== "object") { + return false; + } + const proto = Object.getPrototypeOf(value); + return proto === Object.prototype || proto === null; +} + +/** + * Convert object keys to snake_case (recursively handles nested objects and arrays) + * Preserves Date, Map, Set, RegExp, and other built-in objects unchanged + */ +export function toSnakeCase>(obj: T): T { + const result: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + const snakeKey = camelToSnake(key); + + if (Array.isArray(value)) { + // Recursively convert plain objects inside arrays + result[snakeKey] = value.map((item) => + isPlainObject(item) ? toSnakeCase(item) : item + ); + } else if (isPlainObject(value)) { + result[snakeKey] = toSnakeCase(value); + } else { + // Preserve Date, Map, Set, RegExp, and other built-in objects unchanged + result[snakeKey] = value; + } + } + + return result as T; +} + +/** + * Deep merge two objects + */ +export function mergeDeepRight>( + target: T, + source: Partial +): T { + const output = { ...target }; + + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + const sourceValue = source[key]; + const targetValue = output[key]; + + if ( + sourceValue !== null && + typeof sourceValue === "object" && + !Array.isArray(sourceValue) && + targetValue !== null && + typeof targetValue === "object" && + !Array.isArray(targetValue) + ) { + output[key] = mergeDeepRight( + targetValue as Record, + sourceValue as Record + ) as T[Extract]; + } else if (sourceValue !== undefined) { + output[key] = sourceValue as T[Extract]; + } + } + } + + return output; +} + +/** + * Get action descriptor for logging + */ +export function getActionDescriptor( + type: string, + properties?: Record | null +): string { + if (type === "track" && properties?.event) { + return `track:${properties.event}`; + } + if (type === "screen" && properties?.name) { + return `screen:${properties.name}`; + } + return type; +} + +/** + * Check if error is a network error + */ +export function isNetworkError(error: unknown): boolean { + if (!error) return false; + + const message = error instanceof Error ? error.message : String(error); + const networkErrorMessages = [ + "Network request failed", + "Failed to fetch", + "Network Error", + "timeout", + "ETIMEDOUT", + "ECONNREFUSED", + "ENOTFOUND", + ]; + + return networkErrorMessages.some((msg) => + message.toLowerCase().includes(msg.toLowerCase()) + ); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..b933b7b --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,5 @@ +export * from "./address"; +export * from "./hash"; +export * from "./timestamp"; +export * from "./helpers"; +export * from "./trafficSource"; diff --git a/src/utils/timestamp.ts b/src/utils/timestamp.ts new file mode 100644 index 0000000..ebf0802 --- /dev/null +++ b/src/utils/timestamp.ts @@ -0,0 +1,25 @@ +/** + * Get current time in ISO format + */ +export function getCurrentTimeFormatted(): string { + return new Date().toISOString(); +} + +/** + * Format date to YYYY-MM-DD HH:mm format for hashing + */ +export function toDateHourMinute(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + return `${year}-${month}-${day} ${hours}:${minutes}`; +} + +/** + * Convert milliseconds to seconds + */ +export function millisecondsToSecond(ms: number): number { + return Math.floor(ms / 1000); +} diff --git a/src/utils/trafficSource.ts b/src/utils/trafficSource.ts new file mode 100644 index 0000000..88311e4 --- /dev/null +++ b/src/utils/trafficSource.ts @@ -0,0 +1,132 @@ +/** + * Traffic Source Utilities + * Parse UTM parameters and referral information from URLs + */ + +import { logger } from "../lib/logger"; +import { storage } from "../lib/storage"; +import { SESSION_TRAFFIC_SOURCE_KEY } from "../constants"; +import type { ITrafficSource } from "../types"; + +/** + * Parse UTM parameters and referral info from URL + * Supports both web URLs (https://) and deep link URLs (myapp://) + */ +export function parseTrafficSource(url: string): Partial { + try { + // Handle deep link URLs that may not have standard URL format + let urlObj: URL; + + try { + urlObj = new URL(url); + } catch { + // If URL parsing fails, try to extract query string manually + const queryStart = url.indexOf("?"); + if (queryStart === -1) { + return { referrer: url }; + } + + // Create a fake URL for parsing query params + urlObj = new URL(`http://localhost${url.substring(queryStart)}`); + } + + const params = urlObj.searchParams; + const trafficSource: Partial = {}; + + // Extract UTM parameters + if (params.has("utm_source")) trafficSource.utm_source = params.get("utm_source")!; + if (params.has("utm_medium")) trafficSource.utm_medium = params.get("utm_medium")!; + if (params.has("utm_campaign")) trafficSource.utm_campaign = params.get("utm_campaign")!; + if (params.has("utm_term")) trafficSource.utm_term = params.get("utm_term")!; + if (params.has("utm_content")) trafficSource.utm_content = params.get("utm_content")!; + + // Extract referral codes (check common parameter names) + const refParams = ["ref", "referral", "refcode", "referrer_code"]; + for (const param of refParams) { + if (params.has(param)) { + trafficSource.ref = params.get(param)!; + break; + } + } + + // Store the full URL as referrer + trafficSource.referrer = url; + + return trafficSource; + } catch (error) { + logger.error("Error parsing traffic source from URL:", error); + return { referrer: url }; + } +} + +/** + * Store traffic source in session storage + * Only stores if we have actual UTM or ref data + */ +export function storeTrafficSource(trafficSource: Partial): void { + try { + // Check if we have meaningful data to store (not just referrer) + const hasData = + trafficSource.utm_source || + trafficSource.utm_medium || + trafficSource.utm_campaign || + trafficSource.utm_term || + trafficSource.utm_content || + trafficSource.ref; + + if (hasData || trafficSource.referrer) { + storage().set(SESSION_TRAFFIC_SOURCE_KEY, JSON.stringify(trafficSource)); + logger.debug("Stored traffic source:", trafficSource); + } + } catch (error) { + logger.error("Error storing traffic source:", error); + } +} + +/** + * Get stored traffic source from session + * Returns undefined if no traffic source is stored + */ +export function getStoredTrafficSource(): Partial | undefined { + try { + const stored = storage().get(SESSION_TRAFFIC_SOURCE_KEY); + if (stored && typeof stored === "string") { + return JSON.parse(stored) as Partial; + } + } catch (error) { + logger.debug("Failed to get stored traffic source:", error); + } + return undefined; +} + +/** + * Clear stored traffic source from session + */ +export function clearTrafficSource(): void { + try { + storage().remove(SESSION_TRAFFIC_SOURCE_KEY); + logger.debug("Cleared traffic source from session"); + } catch (error) { + logger.error("Error clearing traffic source:", error); + } +} + +/** + * Merge stored traffic source with current context + * Stored traffic source is used as fallback - current context takes priority + */ +export function mergeWithStoredTrafficSource( + context?: Record +): Record { + const stored = getStoredTrafficSource(); + + if (!stored) { + return context || {}; + } + + // Merge: stored values as base, context values override + return { + ...stored, + ...(context || {}), + }; +} diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..5957b07 --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export const version = "1.0.0"; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..bcec3b2 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "./lib/typescript", + "rootDir": "./src" + }, + "include": ["src"], + "exclude": ["**/__tests__", "**/__mocks__"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..71b5938 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "rootDir": ".", + "paths": { + "@formo/react-native-analytics": ["./src/index"] + }, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["ESNext"], + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "verbatimModuleSyntax": false + }, + "include": ["src"] +}