diff --git a/.eslintignore b/.eslintignore index 68b1c204c..810736b07 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,9 @@ node_modules/ docs/** plugin/build lib/** +integration-tests/hardware/maestro/drivers/** +integration-tests/simulated/** +example/** +example-expo/** +test-peripheral-app/** +test-peripheral-app-ios/** diff --git a/.eslintrc.json b/.eslintrc.json index 031b5b1e3..a8c0c6328 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,36 +8,7 @@ }, "overrides": [ { - "files": ["src/**", "__tests__/**", "integration-tests/**"], - "parser": "hermes-eslint", - "extends": [ - "plugin:react/recommended", - "plugin:flowtype/recommended", - "plugin:prettier/recommended", - "@react-native-community", - "eslint:recommended", - "plugin:jest/recommended", - "plugin:ft-flow/recommended" - ], - "plugins": ["ft-flow", "react", "react-native", "flowtype", "jest", "prettier"], - "rules": { - "ft-flow/define-flow-type": "off", - "ft-flow/use-flow-type": "off", - "flowtype/define-flow-type": "off", - "flowtype/no-types-missing-file-annotation": 0, - "semi": ["error", "never"], - "comma-dangle": ["error", "never"], - "react/display-name": 0, - "ft-flow/no-types-missing-file-annotation": 0, - "jest/no-export": 0 - }, - "globals": { - "$Keys": true, - "$Values": true - } - }, - { - "files": ["*.tsx", "*.ts", "*.d.ts"], + "files": ["src/**/*.tsx", "src/**/*.ts", "src/**/*.d.ts", "__tests__/**/*.ts", "__tests__/**/*.tsx", "plugin/**/*.ts", "plugin/**/*.tsx"], "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", @@ -77,7 +48,10 @@ "varsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" } - ] + ], + "@typescript-eslint/no-redeclare": "off", + "lines-between-class-members": "off", + "@typescript-eslint/lines-between-class-members": "off" }, "env": { "es2020": true @@ -90,6 +64,24 @@ "prettier/prettier": "off" } }, + { + "files": ["src/specs/**"], + "parserOptions": { + "project": ["./tsconfig.specs.json"] + }, + "rules": { + "import/no-default-export": "off" + } + }, + { + "files": ["__tests__/**/*.ts", "__tests__/**/*.tsx"], + "parserOptions": { + "project": ["./tsconfig.tests.json"] + }, + "rules": { + "@typescript-eslint/no-non-null-assertion": "off" + } + }, { "files": ["plugin/**"], "rules": { diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index ea5761b18..000000000 --- a/.flowconfig +++ /dev/null @@ -1,84 +0,0 @@ -[ignore] -; We fork some components by platform -.*/*[.]android.js - -; Ignore "BUCK" generated dirs -/\.buckd/ - -; Ignore polyfills -node_modules/react-native/Libraries/polyfills/.* - -; These should not be required directly -; require from fbjs/lib instead: require('fbjs/lib/warning') -node_modules/warning/.* - -; Flow doesn't support platforms -.*/Libraries/Utilities/LoadingView.js - -; Integration tests -.*/integration-tests/.* - -; Example app -/example/.* -/example-expo/.* - - -; Node modules errors -/node_modules/.* -!/node_modules/react-native/ -/node_modules/react-native/Libraries/DevToolsSettings/DevToolsSettingsManager.d.ts - -; Output files -/lib/.* -/test_project/.* - -[untyped] -.*/node_modules/@react-native-community/cli/.*/.* - -[include] - -[libs] -node_modules/react-native/interface.js -node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow/ -flow/ - -[options] -emoji=true - -module.file_ext=.js -module.file_ext=.ts -module.file_ext=.json -module.file_ext=.ios.js - -exact_by_default=true - -module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' -module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FlowFixMeProps -suppress_type=$FlowFixMeState - -[lints] -sketchy-null-number=warn -sketchy-null-mixed=warn -sketchy-number=warn -untyped-type-import=warn -nonstrict-import=warn -deprecated-type=warn -unsafe-getters-setters=warn -unnecessary-invariant=warn - -[strict] -deprecated-type -nonstrict-import -sketchy-null -unclear-type -unsafe-getters-setters -untyped-import -untyped-type-import - -[version] -^0.259.1 diff --git a/.prettierrc b/.prettierrc index 9ec8574e9..4844e0235 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,7 +7,7 @@ "arrowParens": "avoid", "overrides": [ { - "files": ["src/*"], + "files": ["src/*.js", "src/*.jsx"], "options": { "parser": "babel-flow", "useTabs": false, @@ -16,6 +16,12 @@ "bracketSpacing": true, "jsxBracketSameLine": false } + }, + { + "files": ["src/*.ts", "src/*.tsx", "__tests__/*.ts", "__tests__/*.tsx"], + "options": { + "parser": "typescript" + } } ] } diff --git a/AUDIT.md b/AUDIT.md new file mode 100644 index 000000000..cad92661a --- /dev/null +++ b/AUDIT.md @@ -0,0 +1,218 @@ +# react-native-ble-plx Code Audit + +**Date:** 2026-03-15 +**Version audited:** 3.5.1 (commit `92a496d`) +**Fork:** github.com/iotashan/react-native-ble-plx +**Reviewed by:** Claude Opus 4 + OpenAI Codex (5 parallel review agents) + +--- + +## Executive Summary + +react-native-ble-plx is the most popular React Native BLE library, but it has significant issues: + +- **Thread safety bugs** on both iOS and Android that can cause crashes +- **Broken `enable()` on Android 12+** — always fails due to Activity cast +- **Monitor subscription cleanup is broken** in the JS layer — causes the #1 reported issue +- **No New Architecture (TurboModules/Fabric) support** — critical for RN 0.77+ +- **No modern BLE features** — no BLE 5.0 PHY, no L2CAP, no extended advertising +- **Stale dependencies and documentation** + +The library works for basic BLE use cases but has real concurrency bugs and hasn't kept up with platform evolution. + +--- + +## Maintenance Status + +| Metric | Value | +|--------|-------| +| Current version | 3.5.1 (2026-02-17) | +| Last commit | ~1 month ago | +| React Native tested | 0.77.0 | +| New Architecture | NOT supported | +| iOS minimum | 11.0 (podspec) | +| Android minSdk | 24 (Android 7.0) | +| Android targetSdk | 34 (Android 14) | +| Open issues | 27 | + +**Verdict:** Moderately maintained — bug fixes arrive but docs are stale, no New Arch support, no modern BLE features. + +--- + +## Critical Issues + +### 1. Thread-unsafe shared mutable state (iOS + Android) + +**Both platforms** use plain dictionaries/HashMaps for BLE state (`connectedDevices`, `discoveredServices`, `discoveredCharacteristics`, etc.) accessed from multiple threads without synchronization. + +- **Android:** `HashMap` fields in `BleModule.java:68-72` accessed from RxJava callbacks (IO/computation threads) and React Native method thread +- **iOS:** Mutable dictionaries in `BleModule.swift` accessed from RxSwift callbacks and the method queue + +**Impact:** `ConcurrentModificationException` (Android) or crash on concurrent dictionary access (iOS). Intermittent, hard to reproduce. + +**Fix:** Use `ConcurrentHashMap` (Android) or `DispatchQueue`-synchronized access (iOS). + +### 2. `SafePromise` not thread-safe (iOS) + +The `finished` flag in `SafePromise.swift` is a plain `Bool` with no synchronization. If `resolve` and `reject` race on different threads, the promise can be resolved/rejected twice, crashing React Native. + +### 3. Monitor subscription cleanup broken (JS) + +In `BleManager.js:_handleMonitorCharacteristic`, the returned subscription's `remove()` only cancels the native transaction — it does NOT remove the JS event listener. This causes: +- Duplicate callbacks on subsequent monitors (issue #1308) +- "Stuck" monitors that never clean up (issue #1299) + +**This is the #1 most-reported class of bugs.** + +### 4. `enable()` broken on Android 12+ (API 31+) + +`BleModule.java:1116` attempts to cast `ReactApplicationContext` to `Activity` for `ACTION_REQUEST_ENABLE`, which always fails. On API 33+, `bluetoothAdapter.enable()` throws `SecurityException`. + +**Confirmed by both Claude and Codex reviews.** + +### 5. Empty `AndroidManifestNew.xml` loses permissions + +When using AGP 7.3+ namespace mode, `AndroidManifestNew.xml` (empty) is used instead of the full `AndroidManifest.xml`. All BLE permissions are silently lost. + +--- + +## High Issues + +### 6. Scan listener leak (JS) + +`startDeviceScan` in `BleManager.js:369` creates a new `_scanEventSubscription` without calling `.remove()` on the previous one. Calling `startDeviceScan` twice in succession leaks the old listener. + +### 7. Memory leak: disconnection monitor subscriptions discarded (iOS) + +In `BleModule.swift`, disconnection monitoring subscriptions are created with `_ =` (discarded). If `invalidate()` is called before disconnection, these leak and can fire on deallocated objects. + +### 8. `getState()` crashes without `BLUETOOTH_CONNECT` permission (Android) + +`bluetoothAdapter.getState()` at `BleModule.java:185` requires `BLUETOOTH_CONNECT` on API 31+ but has no runtime permission check. + +### 9. Hardcoded MTU of 23 during scanning (iOS) + +`BleExtensions.swift` returns `mtu = 23` for scanned (not connected) peripherals. Modern BLE devices negotiate much larger MTUs. + +### 10. `requestConnectionPriority` is a no-op on iOS + +Silently succeeds without doing anything or informing the caller. On Android, the 1ms delay parameter is too short to confirm the priority was applied. + +### 11. State restoration race condition (iOS) + +`.amb()` between state change and restoration events can cause restored peripherals to be silently lost if BLE state changes first during app launch. + +--- + +## Medium Issues + +### 12. `_callPromise` creates never-settled promises (JS) + +Every API call creates a `destroyPromise` that is never resolved or rejected — just abandoned. Under heavy use this causes memory pressure. + +### 13. `connectToDevice` silently disconnects on Android + +If the device is already connected, the Android path silently disconnects and reconnects. This is surprising and creates race conditions. + +### 14. TypeScript types are wrong + +- `State`, `LogLevel`, etc. are declared as `enum` in `.d.ts` but are plain objects in JS +- `BleErrorCodeMessage` not exported +- `onDeviceDisconnected` listener has wrong nullable types + +### 15. `disable()` crashes on Android API 33+ + +`bluetoothAdapter.disable()` throws `SecurityException` on API 33+. Called unconditionally. + +### 16. JSON error serialization doesn't escape strings (iOS) + +`BleError.toJS` builds JSON by string interpolation without escaping. Strings with quotes or backslashes produce malformed JSON. + +### 17. Static `IdGenerator` causes cross-instance contamination (Android) + +Static mutable state shared across instances. `clear()` in `destroyClient()` invalidates IDs still in use by another instance. + +### 18. `RxJavaPlugins.setErrorHandler` set globally (Android) + +Global JVM-wide setting that overwrites any other library's error handler. + +### 21. Disconnection event always sends null error (Android) + +`BlePlxModule.java:413` sends `DisconnectionEvent` with `null` error for both normal and abnormal disconnects. GATT error codes (e.g., GATT 133) go to `onErrorCallback` (which rejects the connect promise) but never to the disconnect event listener. Users calling `onDeviceDisconnected` can't distinguish clean disconnects from error-triggered ones. + +### 22. Strong delegate ownership causes lifecycle leak (iOS) + +`BlePlx.m` strongly retains `BleClientManager`, and the delegate is also `strong` — never nilled before teardown. Can cause late callbacks after invalidation. + +### 23. `createClient` not defensive against re-entry (iOS) + +Calling `createClient` twice overwrites `_manager` without invalidating the previous instance. The old adapter keeps running and emitting callbacks. + +### 24. Missing nil guards on `_manager` hang JS promises (iOS) + +After `destroyClient` sets `_manager = nil`, subsequent method calls message nil. ObjC nil-messaging silently does nothing, so `resolve`/`reject` never fires — JS promises hang forever. + +### 25. `invalidate` doesn't call `[super invalidate]` (iOS) + +`BlePlx.m:68` overrides `invalidate` without calling super. `RCTEventEmitter` base class cleanup (listener count reset, etc.) is skipped. + +--- + +## Final Tally + +**25 issues total** found by 5 independent review agents (3 Claude + 2 Codex): +- **5 critical** (thread safety x2, monitor cleanup, enable() broken, empty manifest) +- **8 high** (scan leak, memory leak, permission crash, MTU, lifecycle leaks, re-entry, nil guards, state restoration) +- **9 medium** (promise leaks, wrong types, JSON injection, static contamination, disconnect errors, etc.) +- **3 low** (naming inconsistency, deprecated getValue(), ID precision) + +`BlePlxModule.java:413` sends `DisconnectionEvent` with `null` error for both normal and abnormal disconnects. GATT error codes (e.g., GATT 133) go to `onErrorCallback` (which rejects the connect promise) but never to the disconnect event listener. Users calling `onDeviceDisconnected` can't distinguish clean disconnects from error-triggered ones. + +### 19. Promise double-resolution via `onDisposed` (iOS) + +Every Rx subscription has both completion handlers and `onDisposed` that tries to reject with "cancelled." Relies on fragile `SafePromise` flag (which itself is not thread-safe — see #2). + +### 20. `RestoredState.scanOptions` reads wrong key (iOS) + +Copy-paste bug: reads `CBCentralManagerRestoredStatePeripheralsKey` instead of `CBCentralManagerRestoredStateScanOptionsKey`. + +--- + +## Missing Modern BLE Features + +| Feature | iOS | Android | +|---------|-----|---------| +| BLE 5.0 PHY (2M, Coded) | No | No | +| Extended Advertising | No | Partial (legacyScan flag) | +| L2CAP Channels | Delegate exists but dead code | No | +| Connection Events (iOS 13+) | No | N/A | +| LE Audio / LC3 | No | No | +| Companion Device Manager | N/A | No | +| iOS 16+ disconnect with reconnection | No | N/A | +| New Architecture (TurboModules) | No | No | + +--- + +## Fixable GitHub Issues + +| Issue | Title | Root Cause | Fix Location | +|-------|-------|-----------|-------------| +| #1308 | Duplicate monitor responses | JS subscription cleanup bug | `BleManager.js:_handleMonitorCharacteristic` | +| #1299 | Monitor callback stuck | Same as #1308 | Same | +| #1279 | Device not showing after disconnect | Scan listener leak | `BleManager.js:startDeviceScan` | +| #1267 | `enable()` deprecated API 33 | Missing deprecation warning | `BleManager.js:enable()` | +| #852 | Ensuring subscription ready | No ready callback | `BleManager.js:_handleMonitorCharacteristic` | +| #1160 | onDisconnected never reporting error | JS event filtering | `BleManager.js:onDeviceDisconnected` | + +--- + +## Strategic Recommendations + +1. **Fix the monitor subscription cleanup** — addresses the top class of reported bugs +2. **Fix thread safety** on both platforms — use synchronized collections +3. **Add New Architecture support** — RN 0.77 defaults to New Arch; this is the biggest strategic gap +4. **Fix `enable()` on Android 12+** — or remove it and document the alternative +5. **Fix the empty manifest** for AGP namespace mode +6. **Update dependencies** — remove `@types/react-native`, upgrade RxAndroidBle to v3, update vendored RxSwift +7. **Add BLE 5.0 features** — PHY selection, extended advertising +8. **Fix TypeScript types** — enums vs const objects, missing exports diff --git a/CHANGELOG.md b/CHANGELOG.md index af62d095d..573b2e66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,58 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [4.0.0-alpha.0] - 2026-03-16 + +### Changed + +- Complete rewrite as a TurboModule for React Native New Architecture (0.82+) +- Android native layer rewritten in Kotlin using Nordic Android-BLE-Library +- iOS native layer rewritten in Swift with actor-based CoreBluetooth wrapper +- `State`, `ConnectionPriority`, `ConnectionState`, `LogLevel` changed from TypeScript enums to `as const` objects +- `BleManager` constructor no longer auto-initializes — `createClient()` must be called explicitly +- `BleManager` is no longer a silent singleton — each instance is independent +- Monitor subscription `.remove()` now properly cleans up both native and JS listeners +- All values remain Base64-encoded (same as v3) +- Minimum React Native version raised to 0.82.0 +- Minimum iOS version raised to 15 +- Minimum Android API raised to 23 + +### Added + +- TurboModule with Codegen typed events (no manual NativeEventEmitter setup) +- `requestPhy()` and `readPhy()` for BLE 5.0 PHY selection (Android) +- `openL2CAPChannel()`, `writeL2CAPChannel()`, `closeL2CAPChannel()` for L2CAP streams (iOS) +- `requestConnectionPriority()` for Android connection priority hints +- `getAuthorizationStatus()` for iOS Bluetooth authorization state +- `onConnectionEvent()` for iOS 13+ connection events +- `onBondStateChange()` for Android bond state monitoring +- `onRestoreState()` for iOS background state restoration +- Connection retry with `retries` and `retryDelay` options in `connectToDevice()` +- Auto-MTU 517 negotiation on Android connect +- Event batching for scan results and characteristic notifications +- `BleError` unified cross-platform error model with rich diagnostic fields +- `MonitorOptions` with `batchInterval` and `subscriptionType` parameters +- `BleManagerOptions` with `scanBatchIntervalMs` constructor option +- Complete documentation rewrite: README, Getting Started, API Reference, Migration Guide, Troubleshooting, E2E Testing Guide + +### Removed + +- `enable()` and `disable()` (broken on Android 12+, no-op on iOS) +- `setLogLevel()` (not exposed in TurboModule interface) +- Bridge-based native modules (New Architecture only) +- Support for React Native < 0.82 + +### Fixed + +- All 25 issues from the v3 code audit +- Thread-unsafe shared state on both platforms +- Monitor subscription cleanup leak (#1308, #1299) +- Hardcoded MTU 23 on iOS for scanned devices +- State restoration race condition on iOS +- Never-settled promises (every operation now has a timeout) +- Android disconnection always reporting null error +- Promise double-resolution on iOS + ## [3.5.1] - 2026-02-17 ### Changed diff --git a/README.md b/README.md index 57cecaedc..14490533b 100644 --- a/README.md +++ b/README.md @@ -7,77 +7,46 @@ /> -## About this library +# react-native-ble-plx -It supports: +A React Native library for talking to Bluetooth Low Energy peripherals. Built as a TurboModule for the New Architecture from the ground up. -- [observing device's Bluetooth adapter state](https://github.com/dotintent/react-native-ble-plx/wiki/Bluetooth-Adapter-State) -- [scanning BLE devices](https://github.com/dotintent/react-native-ble-plx/wiki/Bluetooth-Scanning) -- [making connections to peripherals](https://github.com/dotintent/react-native-ble-plx/wiki/Device-Connecting) -- [discovering services/characteristics](https://github.com/dotintent/react-native-ble-plx/wiki/Device-Service-Discovery) -- [reading](https://github.com/dotintent/react-native-ble-plx/wiki/Characteristic-Reading)/[writing](https://github.com/dotintent/react-native-ble-plx/wiki/Characteristic-Writing) characteristics -- [observing characteristic notifications/indications](https://github.com/dotintent/react-native-ble-plx/wiki/Characteristic-Notifying) -- [reading RSSI](https://github.com/dotintent/react-native-ble-plx/wiki/RSSI-Reading) -- [negotiating MTU](https://github.com/dotintent/react-native-ble-plx/wiki/MTU-Negotiation) -- [background mode on iOS]() -- turning the device's Bluetooth adapter on +Yes, BLE is hard. No, this library won't make it easy -- but it'll make it possible without losing your mind. -It does NOT support: +## What's New in v4 -- bluetooth classic devices. -- communicating between phones using BLE (Peripheral support) -- [bonding peripherals](https://github.com/dotintent/react-native-ble-plx/wiki/Device-Bonding) -- [beacons](https://github.com/dotintent/react-native-ble-plx/wiki/=-FAQ:-Beacons) +v4 is a complete rewrite. The old Bridge-based native code is gone, replaced by: -## Table of Contents - -1. [Compatibility](#compatibility) -2. [Recent Changes](#recent-changes) -3. [Documentation & Support](#documentation--support) -4. [Configuration & Installation](#configuration--installation) -5. [Troubleshooting](#troubleshooting) -6. [Contributions](#contributions) +- **TurboModule with Codegen** -- typed native events, no more `NativeEventEmitter` manual wiring +- **Fabric / New Architecture only** -- React Native 0.82+ required (legacy arch was removed in 0.82) +- **Android: Nordic BLE Library** -- proper GATT operation queuing, auto-MTU 517, coroutine-based +- **iOS: Swift actor + CoreBluetooth** -- thread-safe by design, no more delegate spaghetti +- **Modern BLE features** -- PHY selection, L2CAP channels, connection events, bond state monitoring +- **Unified error model** -- rich, cross-platform `BleError` with platform diagnostics +- **Event batching** -- configurable backpressure so notification storms don't crash your JS thread +- **25 audit issues fixed** -- every known bug from the v3 audit is resolved ## Compatibility -For old RN versions (<0.60) please check [old README](./docs/README_V1.md) (1.x) -for the old instructions or [migration guide](./docs/MIGRATION_V1.md). - -| React Native | 3.1.2 | -| ------------ | ------------------ | -| 0.74.1 | :white_check_mark: | -| 0.69.6 | :white_check_mark: | -| Expo 51 | :white_check_mark: | - -## Recent Changes - -**3.2.0** - -- Added Android Instance checking before calling its method, an error will be visible on the RN side -- Added information related to Android 14 to the documentation. -- Changed destroyClient, cancelTransaction, setLogLevel, startDeviceScan, stopDeviceScan calls to promises to allow error reporting if it occurs. -- Fixed one of the functions calls that clean up the BLE instance after it is destroyed. - -[Current version changes](CHANGELOG.md) -[All previous changes](CHANGELOG-pre-3.0.0.md) +| Requirement | Minimum | +|-------------|---------| +| React Native | >= 0.82.0 | +| Expo SDK | 55+ | +| iOS | 15+ | +| Android API | 23+ | +| Architecture | New Architecture (TurboModule) | -## Documentation & Support +This library does **not** work with Expo Go. You need a [development build](https://docs.expo.dev/develop/development-builds/introduction/). -Interested in React Native project involving Bluetooth Low Energy? [We can help you!](https://withintent.com/?utm_source=github&utm_medium=github&utm_campaign=external_traffic) +## 60-Second Quickstart -[Documentation can be found here](https://dotintent.github.io/react-native-ble-plx/). +### Expo (recommended) -Contact us at [intent](https://withintent.com/contact-us/?utm_source=github&utm_medium=github&utm_campaign=external_traffic). - -## Configuration & Installation - -### Expo SDK 43+ - -> Tested against Expo SDK 49 -> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/). -> First install the package with yarn, npm, or [`npx expo install`](https://docs.expo.io/workflow/expo-cli/#expo-install). +```bash +npx expo install react-native-ble-plx +``` -After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`: +Add the config plugin to your `app.json`: ```json { @@ -87,123 +56,143 @@ After installing this npm package, add the [config plugin](https://docs.expo.io/ } ``` -Then you should build the version using native modules (e.g. with `npx expo prebuild` command). -And install it directly into your device with `npx expo run:android`. - -You can find more details in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide. +Build and run on a physical device: -## API - -The plugin provides props for extra customization. Every time you change the props or plugins, you'll need to rebuild (and `prebuild`) the native app. If no extra properties are added, defaults will be used. +```bash +npx expo run:ios --device +``` -- `isBackgroundEnabled` (_boolean_): Enable background BLE support on Android. Adds `` to the `AndroidManifest.xml`. Default `false`. -- `neverForLocation` (_boolean_): Set to true only if you can strongly assert that your app never derives physical location from Bluetooth scan results. The location permission will be still required on older Android devices. Note, that some BLE beacons are filtered from the scan results. Android SDK 31+. Default `false`. _WARNING: This parameter is experimental and BLE might not work. Make sure to test before releasing to production._ -- `modes` (_string[]_): Adds iOS `UIBackgroundModes` to the `Info.plist`. Options are: `peripheral`, and `central`. Defaults to undefined. -- `bluetoothAlwaysPermission` (_string | false_): Sets the iOS `NSBluetoothAlwaysUsageDescription` permission message to the `Info.plist`. Setting `false` will skip adding the permission. Defaults to `Allow $(PRODUCT_NAME) to connect to bluetooth devices`. +### Bare React Native -> Expo SDK 48 supports iOS 13+ which means `NSBluetoothPeripheralUsageDescription` is fully deprecated. It is no longer setup in `@config-plugins/react-native-ble-plx@5.0.0` and greater. +```bash +npm install react-native-ble-plx +cd ios && pod install +``` -#### Example +### Then, regardless of how you got here: + +```typescript +import { BleManager, State } from 'react-native-ble-plx'; + +const manager = new BleManager(); +await manager.createClient(); + +// Wait for Bluetooth to be ready +const currentState = await manager.state(); +if (currentState !== State.PoweredOn) { + await new Promise(resolve => { + const sub = manager.onStateChange(state => { + if (state === State.PoweredOn) { + sub.remove(); + resolve(); + } + }); + }); +} -```json -{ - "expo": { - "plugins": [ - [ - "react-native-ble-plx", - { - "isBackgroundEnabled": true, - "modes": ["peripheral", "central"], - "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices" - } - ] - ] +// Scan for devices +manager.startDeviceScan(null, null, (error, device) => { + if (error) return console.error(error); + if (device?.name === 'MyDevice') { + manager.stopDeviceScan(); + connectAndRead(device.id); } +}); + +async function connectAndRead(deviceId: string) { + const device = await manager.connectToDevice(deviceId); + await manager.discoverAllServicesAndCharacteristics(deviceId); + const char = await manager.readCharacteristicForDevice( + deviceId, + 'service-uuid-here', + 'characteristic-uuid-here' + ); + console.log('Value:', char.value); // Base64-encoded } ``` -### Legacy Expo (SDK < 43) +That's it. For the full setup (permissions, platform config, Expo), see the [Getting Started guide](docs/GETTING_STARTED.md). -1. Make sure your Expo project is ejected (formerly: detached). You can read how to do it [here](https://docs.expo.dev/expokit/eject/). (only for Expo SDK < 43) -2. Follow steps for iOS/Android. +## Example Apps -### iOS ([example setup](https://github.com/Cierpliwy/SensorTag)) +- **[Expo example](example-expo/)** -- Expo SDK 55, expo-router. Start here. +- **[Bare RN example](example/)** -- React Native 0.84, React Navigation. For the brave. -1. `npm install --save react-native-ble-plx` -1. Enter `ios` folder and run `pod update` -1. Add `NSBluetoothAlwaysUsageDescription` in `info.plist` file. (it is a requirement since iOS 13) -1. If you want to support background mode: - - In your application target go to `Capabilities` tab and enable `Uses Bluetooth LE Accessories` in - `Background Modes` section. - - Pass `restoreStateIdentifier` and `restoreStateFunction` to `BleManager` constructor. +## Documentation -### Android ([example setup](https://github.com/Cierpliwy/SensorTag)) +- **[Getting Started](docs/GETTING_STARTED.md)** -- Installation, permissions, platform setup, first scan +- **[API Reference](docs/API.md)** -- Every method on `BleManager`, fully typed +- **[Migration Guide (v3 to v4)](docs/MIGRATION_V3_TO_V4.md)** -- What changed, what broke, how to fix it +- **[Troubleshooting](docs/TROUBLESHOOTING.md)** -- Common problems and their solutions +- **[E2E Testing](docs/TESTING.md)** -- Hardware test infrastructure and maestro-runner flows -1. `npm install --save react-native-ble-plx` -1. In top level `build.gradle` make sure that min SDK version is at least 23: +## What This Library Does - ```groovy - buildscript { - ext { - ... - minSdkVersion = 23 - ... - ``` +- Observe Bluetooth adapter state +- Scan for BLE peripherals (including BLE 5.0 extended advertising) +- Connect to peripherals with configurable retry and timeout +- Discover services and characteristics +- Read, write (with and without response), and monitor characteristics +- Subscribe to notifications and indications +- Negotiate MTU +- Request PHY (BLE 5.0, Android) +- Open L2CAP channels (iOS) +- Monitor bond state changes (Android) +- iOS background mode with state restoration -1. In `build.gradle` make sure to add jitpack repository to known repositories: +## What This Library Does NOT Do - ```groovy - allprojects { - repositories { - ... - maven { url 'https://www.jitpack.io' } - } - } - ``` +- Bluetooth Classic -- BLE only +- Peripheral/server role -- central only +- Beacons -- use a dedicated beacon library +- LE Audio / LC3 codec +- Web or desktop platforms -1. In `AndroidManifest.xml`, add Bluetooth permissions and update ``: +## Migrating from v3 - ```xml - +The API surface is intentionally similar to v3, but there are breaking changes. The big ones: - ... +- `enable()` and `disable()` are gone (broken on Android 12+, use system settings) +- `State`, `ConnectionPriority`, etc. are `const` objects, not TypeScript enums +- Monitor subscription `.remove()` now actually cleans up (yes, it was broken before) +- Requires React Native 0.82+ (New Architecture only) - - - - - - - - +Full details in the [Migration Guide](docs/MIGRATION_V3_TO_V4.md). - - +## Expo Config Plugin - ... - ``` +The quickstart above covers the basics. If you need background BLE or want to customize permissions, pass options: -1. (Optional) In SDK 31+ You can remove `ACCESS_FINE_LOCATION` (or mark it as `android:maxSdkVersion="30"` ) from `AndroidManifest.xml` and add `neverForLocation` flag into `BLUETOOTH_SCAN` permissions which says that you will not use location based on scanning eg: +```json +{ + "expo": { + "plugins": [ + [ + "react-native-ble-plx", + { + "isBackgroundEnabled": true, + "modes": ["central"], + "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices" + } + ] + ] + } +} +``` - ```xml - - - - - - - - +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `isBackgroundEnabled` | boolean | `false` | Add `bluetooth-central` to iOS background modes | +| `modes` | string[] | `[]` | iOS `UIBackgroundModes`: `"peripheral"`, `"central"` | +| `bluetoothAlwaysPermission` | string \| false | `'Allow $(PRODUCT_NAME) to connect to bluetooth devices'` | iOS `NSBluetoothAlwaysUsageDescription` | +| `neverForLocation` | boolean | `true` | If true, adds `neverForLocation` flag to Android `BLUETOOTH_SCAN` | - ... - ``` +Full details in the [Getting Started guide](docs/GETTING_STARTED.md). - With `neverForLocation` flag active, you no longer need to ask for `ACCESS_FINE_LOCATION` in your app +## Contributing -## Troubleshooting +PRs welcome. If you're fixing a bug, include a test case or at minimum describe how to reproduce it. If you're adding a feature, open an issue first so we can discuss the API. -## Contributions +## License -- Special thanks to @EvanBacon for supporting the expo config plugin. +Apache License 2.0 diff --git a/__tests__/BleError.test.ts b/__tests__/BleError.test.ts new file mode 100644 index 000000000..29f882f92 --- /dev/null +++ b/__tests__/BleError.test.ts @@ -0,0 +1,30 @@ +// __tests__/BleError.test.ts +import { BleError, BleErrorCode } from '../src/BleError' + +test('BleError constructs with all fields', () => { + const err = new BleError({ + code: BleErrorCode.ConnectionFailed, + message: 'GATT error 133', + isRetryable: true, + platform: 'android', + gattStatus: 133, + deviceId: 'AA:BB:CC:DD:EE:FF', + operation: 'connect', + nativeDomain: null, + nativeCode: null, + serviceUuid: null, + characteristicUuid: null, + attErrorCode: null + }) + expect(err.code).toBe(BleErrorCode.ConnectionFailed) + expect(err.isRetryable).toBe(true) + expect(err.gattStatus).toBe(133) + expect(err.deviceId).toBe('AA:BB:CC:DD:EE:FF') + expect(err instanceof Error).toBe(true) +}) + +test('BleErrorCode values are correct', () => { + expect(BleErrorCode.DeviceNotFound).toBe(0) + expect(BleErrorCode.ManagerNotInitialized).toBe(400) + expect(BleErrorCode.UnknownError).toBe(999) +}) diff --git a/__tests__/BleManager.js b/__tests__/BleManager.js deleted file mode 100644 index 074530174..000000000 --- a/__tests__/BleManager.js +++ /dev/null @@ -1,412 +0,0 @@ -/* eslint-disable no-import-assign */ -import { BleManager, Device, Service, Characteristic } from '../src' -import { BleErrorCode, BleErrorCodeMessage } from '../src/BleError' -import * as Native from '../src/BleModule' - -import { NativeEventEmitter } from './Utils' -import { Descriptor } from '../src/Descriptor' -Native.EventEmitter = NativeEventEmitter - -var bleManager -const restoreStateFunction = jest.fn() - -// This type of error is passed in async and event case. -const nativeOperationCancelledError = - '{"errorCode": 2, "attErrorCode": null, "iosErrorCode": null, "reason": null, "androidErrorCode": null}' - -beforeEach(() => { - Native.BleModule = { - createClient: jest.fn(), - destroyClient: jest.fn(), - cancelTransaction: jest.fn(), - setLogLevel: jest.fn(), - logLevel: jest.fn(), - enable: jest.fn(), - disable: jest.fn(), - state: jest.fn(), - startDeviceScan: jest.fn(), - stopDeviceScan: jest.fn(), - readRSSIForDevice: jest.fn(), - connectToDevice: jest.fn(), - cancelDeviceConnection: jest.fn(), - isDeviceConnected: jest.fn(), - discoverAllServicesAndCharacteristicsForDevice: jest.fn(), - servicesForDevice: jest.fn(), - characteristicsForDevice: jest.fn(), - descriptorsForDevice: jest.fn(), - readCharacteristicForDevice: jest.fn(), - writeCharacteristicForDevice: jest.fn(), - monitorCharacteristicForDevice: jest.fn(), - readDescriptorForDevice: jest.fn(), - writeDescriptorForDevice: jest.fn(), - requestMTUForDevice: jest.fn(), - requestConnectionPriorityForDevice: jest.fn(), - ScanEvent: 'scan_event', - ReadEvent: 'read_event', - StateChangeEvent: 'state_change_event', - DisconnectionEvent: 'disconnection_event' - } - bleManager = new BleManager({ - restoreStateIdentifier: 'identifier', - restoreStateFunction - }) -}) - -test('BleModule calls create function when BleManager is constructed', () => { - expect(Native.BleModule.createClient).toBeCalledWith('identifier') - expect(Native.BleModule.destroyClient).not.toBeCalled() -}) - -test('BleModule emits state restoration after BleManager was created', () => { - const restoredState = { - connectedPeripherals: [new Device({ id: 'deviceId' }, bleManager)] - } - Native.BleModule.emit(Native.BleModule.RestoreStateEvent, restoredState) - expect(restoreStateFunction).toBeCalledWith(restoredState) -}) - -test('BleModule calls destroy function when destroyed', () => { - bleManager.destroy() - expect(Native.BleModule.createClient).toBeCalled() - expect(Native.BleModule.destroyClient).toBeCalled() -}) - -test('BleModule calls enable function when enabled', async () => { - expect(await bleManager.enable('tid')).toBe(bleManager) - expect(Native.BleModule.enable).toBeCalledWith('tid') -}) - -test('BleModule calls disable function when disabled', async () => { - expect(await bleManager.disable('tid')).toBe(bleManager) - expect(Native.BleModule.disable).toBeCalledWith('tid') -}) - -test('BleModule calls setLogLevel function when logLevel is modified', () => { - bleManager.setLogLevel('Debug') - expect(Native.BleModule.setLogLevel).toBeCalledWith('Debug') -}) - -test('BleModule calls logLevel function when logLevel is retrieved', async () => { - Native.BleModule.logLevel = jest.fn().mockReturnValueOnce(Promise.resolve('Verbose')) - const logLevel = await bleManager.logLevel() - expect(Native.BleModule.logLevel).toBeCalled() - expect(logLevel).toBe('Verbose') -}) - -test('BleManager state function should return BleModule state', async () => { - Native.BleModule.state = jest - .fn() - .mockReturnValueOnce(Promise.resolve('PoweredOff')) - .mockReturnValueOnce(Promise.resolve('Resetting')) - - expect(await bleManager.state()).toBe('PoweredOff') - expect(await bleManager.state()).toBe('Resetting') -}) - -test('BleModule two emitted state changes are registered by BleManager', () => { - const newStateCallback = jest.fn() - bleManager.onStateChange(newStateCallback) - expect(newStateCallback).not.toBeCalled() - Native.BleModule.emit(Native.BleModule.StateChangeEvent, 'PoweredOn') - Native.BleModule.emit(Native.BleModule.StateChangeEvent, 'PoweredOff') - expect(newStateCallback.mock.calls).toEqual([['PoweredOn'], ['PoweredOff']]) -}) - -test('When BleManager cancelTransaction is called it should call BleModule cancelTransaction', () => { - bleManager.cancelTransaction('id') - expect(Native.BleModule.cancelTransaction).toBeCalledWith('id') -}) - -test('When BleManager starts scanning it calls BleModule startScanning function', () => { - const listener = jest.fn() - bleManager.startDeviceScan(['18a0', '1800'], { allowDuplicates: true }, listener) - expect(Native.BleModule.startDeviceScan).toBeCalledWith(['18a0', '1800'], { - allowDuplicates: true - }) -}) - -test('When BleManager while scanning emits an error it calls listener with error', () => { - const listener = jest.fn() - bleManager.startDeviceScan(null, null, listener) - Native.BleModule.emit(Native.BleModule.ScanEvent, [nativeOperationCancelledError, null]) - expect(listener.mock.calls.length).toBe(1) - expect(listener.mock.calls[0][0].message).toBe(BleErrorCodeMessage[BleErrorCode.OperationCancelled]) -}) - -test('When BleManager stops scanning it calls BleModule stopScanning function', () => { - bleManager.stopDeviceScan() - expect(Native.BleModule.stopDeviceScan).toBeCalled() -}) - -test('When BleManager readRSSI is called it should call BleModule readRSSI', () => { - bleManager.readRSSIForDevice('id') - expect(Native.BleModule.readRSSIForDevice).toBeCalledWith('id', '2') - bleManager.readRSSIForDevice('id', 'transaction') - expect(Native.BleModule.readRSSIForDevice).toBeCalledWith('id', 'transaction') -}) - -test('When BleManager calls async function which throws it should return Unknown Error', async () => { - Native.BleModule.readRSSIForDevice.mockImplementationOnce(async () => { - throw new Error('Unexpected error2') - }) - await expect(bleManager.readRSSIForDevice('id')).rejects.toThrowError(BleErrorCodeMessage[BleErrorCode.UnknownError]) -}) - -test('When BleManager calls async function which valid JSON object should return specific error', async () => { - Native.BleModule.readRSSIForDevice.mockImplementationOnce(async () => { - throw new Error(nativeOperationCancelledError) - }) - await expect(bleManager.readRSSIForDevice('id')).rejects.toThrowError( - BleErrorCodeMessage[BleErrorCode.OperationCancelled] - ) -}) - -test('When BleManager scans two devices it passes them to callback function', () => { - Native.BleModule.emit(Native.BleModule.ScanEvent, [null, { id: '1' }]) - const listener = jest.fn() - - bleManager.startDeviceScan(null, null, listener) - Native.BleModule.emit(Native.BleModule.ScanEvent, [null, { id: '2' }]) - Native.BleModule.emit(Native.BleModule.ScanEvent, [null, { id: '3' }]) - bleManager.stopDeviceScan() - Native.BleModule.emit(Native.BleModule.ScanEvent, [null, { id: '4' }]) - - expect(listener.mock.calls.length).toBe(2) - expect(listener.mock.calls[0][0]).toBeFalsy() - expect(listener.mock.calls[0][1].id).toBe('2') - expect(listener.mock.calls[1][0]).toBeFalsy() - expect(listener.mock.calls[1][1].id).toBe('3') - expect(Native.BleModule.startDeviceScan).toBeCalled() - expect(Native.BleModule.stopDeviceScan).toBeCalled() -}) - -test('When BleManager calls connectToDevice equivalent BleModule function should be called', async () => { - Native.BleModule.connectToDevice = jest.fn().mockReturnValue(Promise.resolve({ id: 'id' })) - expect(await bleManager.connectToDevice('id', {})).toBeInstanceOf(Device) - expect(Native.BleModule.connectToDevice).toBeCalledWith('id', {}) - expect((await bleManager.connectToDevice('id', {})).id).toBe('id') -}) - -test('When BleManager calls cancelDeviceConnection equivalent BleModule function should be called', async () => { - Native.BleModule.cancelDeviceConnection = jest.fn().mockReturnValue(Promise.resolve({ id: 'id' })) - expect(await bleManager.cancelDeviceConnection('id')).toBeInstanceOf(Device) - expect(Native.BleModule.cancelDeviceConnection).toBeCalledWith('id') - expect((await bleManager.cancelDeviceConnection('id')).id).toBe('id') -}) - -test('BleManager monitors device disconnection properly', () => { - const listener = jest.fn() - - Native.BleModule.emit(Native.BleModule.DisconnectionEvent, [null, { id: 'id' }]) - const subscription = bleManager.onDeviceDisconnected('id', listener) - Native.BleModule.emit(Native.BleModule.DisconnectionEvent, [null, { id: 'id2' }]) - Native.BleModule.emit(Native.BleModule.DisconnectionEvent, [null, { id: 'id' }]) - subscription.remove() - Native.BleModule.emit(Native.BleModule.DisconnectionEvent, [null, { id: 'id' }]) - - expect(listener.mock.calls.length).toBe(1) - expect(listener.mock.calls[0][0]).toBeFalsy() - expect(listener.mock.calls[0][1]).toBeInstanceOf(Device) - expect(listener.mock.calls[0][1].id).toBe('id') -}) - -test('BleManager handles errors properly while monitoring disconnections', () => { - const listener = jest.fn() - const subscription = bleManager.onDeviceDisconnected('id', listener) - Native.BleModule.emit(Native.BleModule.DisconnectionEvent, [nativeOperationCancelledError, { id: 'id' }]) - subscription.remove() - expect(listener.mock.calls.length).toBe(1) - expect(listener.mock.calls[0][0].message).toBe(BleErrorCodeMessage[BleErrorCode.OperationCancelled]) -}) - -test('BleManager calls BleModule isDeviceConnected function properly', async () => { - Native.BleModule.isDeviceConnected = jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true) - expect(await bleManager.isDeviceConnected('id')).toBe(false) - expect(await bleManager.isDeviceConnected('id')).toBe(true) - expect(Native.BleModule.isDeviceConnected.mock.calls.length).toBe(2) -}) - -test('BleManager properly calls BleModule discovery function', async () => { - Native.BleModule.discoverAllServicesAndCharacteristicsForDevice = jest - .fn() - .mockReturnValueOnce(Promise.resolve({ id: 'id' })) - const device = await bleManager.discoverAllServicesAndCharacteristicsForDevice('id', 'tid') - expect(device).toBeInstanceOf(Device) - expect(device.id).toBe('id') - expect(Native.BleModule.discoverAllServicesAndCharacteristicsForDevice).toBeCalledWith('id', 'tid') -}) - -test('BleManager properly calls servicesForDevice BleModule function', async () => { - Native.BleModule.servicesForDevice = jest.fn().mockReturnValueOnce( - Promise.resolve([ - { uuid: 'a', deviceId: 'id' }, - { uuid: 'b', deviceId: 'id' } - ]) - ) - const services = await bleManager.servicesForDevice('id') - expect(services.length).toBe(2) - expect(services[0]).toBeInstanceOf(Service) - expect(services[1]).toBeInstanceOf(Service) - expect(services[0].uuid).toBe('a') - expect(services[1].uuid).toBe('b') - expect(Native.BleModule.servicesForDevice).toBeCalledWith('id') -}) - -test('BleManager properly calls characteristicsForDevice BleModule function', async () => { - Native.BleModule.characteristicsForDevice = jest.fn().mockReturnValueOnce( - Promise.resolve([ - { uuid: 'a', deviceId: 'id' }, - { uuid: 'b', deviceId: 'id' } - ]) - ) - const characteristics = await bleManager.characteristicsForDevice('id', 'aa') - expect(characteristics.length).toBe(2) - expect(characteristics[0]).toBeInstanceOf(Characteristic) - expect(characteristics[1]).toBeInstanceOf(Characteristic) - expect(characteristics[0].uuid).toBe('a') - expect(characteristics[1].uuid).toBe('b') - expect(Native.BleModule.characteristicsForDevice).toBeCalledWith('id', 'aa') -}) - -test('BleManager properly calls descriptorsForDevice BleModule function', async () => { - Native.BleModule.descriptorsForDevice = jest.fn().mockReturnValueOnce( - Promise.resolve([ - { uuid: 'a', deviceId: 'id' }, - { uuid: 'b', deviceId: 'id' } - ]) - ) - const descriptors = await bleManager.descriptorsForDevice('deviceId', 'serviceUUID', 'characteristicUUID') - expect(descriptors.length).toBe(2) - expect(descriptors[0]).toBeInstanceOf(Descriptor) - expect(descriptors[1]).toBeInstanceOf(Descriptor) - expect(descriptors[0].uuid).toBe('a') - expect(descriptors[1].uuid).toBe('b') - expect(Native.BleModule.descriptorsForDevice).toBeCalledWith('deviceId', 'serviceUUID', 'characteristicUUID') -}) - -test('BleManager properly reads characteristic value', async () => { - Native.BleModule.readCharacteristicForDevice = jest - .fn() - .mockReturnValueOnce(Promise.resolve({ uuid: 'aaaa', value: '=AA' })) - const newCharacteristicValue = await bleManager.readCharacteristicForDevice('id', 'bbbb', 'aaaa', 'ok') - expect(newCharacteristicValue).toBeInstanceOf(Characteristic) - expect(newCharacteristicValue.uuid).toBe('aaaa') - expect(newCharacteristicValue.value).toBe('=AA') - expect(Native.BleModule.readCharacteristicForDevice).toBeCalledWith('id', 'bbbb', 'aaaa', 'ok') -}) - -test('BleManager properly writes characteristic value', async () => { - Native.BleModule.writeCharacteristicForDevice = jest - .fn() - .mockReturnValue(Promise.resolve({ uuid: 'aaaa', value: '=AA' })) - - const options = [ - { - response: true, - function: bleManager.writeCharacteristicWithResponseForDevice.bind(bleManager) - }, - { - response: false, - function: bleManager.writeCharacteristicWithoutResponseForDevice.bind(bleManager) - } - ] - - for (let option of options) { - const characteristic = await option.function('id', 'aaaa', 'bbbb', '=AA', 'trans') - expect(characteristic).toBeInstanceOf(Characteristic) - expect(characteristic.uuid).toBe('aaaa') - expect(characteristic.value).toBe('=AA') - expect(Native.BleModule.writeCharacteristicForDevice).toBeCalledWith( - 'id', - 'aaaa', - 'bbbb', - '=AA', - option.response, - 'trans' - ) - } -}) - -test('BleManager properly monitors characteristic value', async () => { - const listener = jest.fn() - Native.BleModule.monitorCharacteristicForDevice = jest.fn().mockReturnValue(Promise.resolve(null)) - - Native.BleModule.emit(Native.BleModule.ReadEvent, [null, { id: 'a', value: 'a' }, 'id']) - Native.BleModule.emit(Native.BleModule.ReadEvent, [null, { id: 'a', value: 'b' }, 'x']) - const subscription = bleManager.monitorCharacteristicForDevice('id', 'aaaa', 'bbbb', listener, 'x') - Native.BleModule.emit(Native.BleModule.ReadEvent, [null, { id: 'a', value: 'b' }, 'x']) - Native.BleModule.emit(Native.BleModule.ReadEvent, [null, { id: 'a', value: 'b' }, 'x']) - Native.BleModule.emit(Native.BleModule.ReadEvent, [null, { id: 'a', value: 'c' }, 'x2']) - subscription.remove() - expect(listener).toHaveBeenCalledTimes(2) - expect(Native.BleModule.cancelTransaction).toBeCalledWith('x') - expect(Native.BleModule.monitorCharacteristicForDevice).toBeCalledWith('id', 'aaaa', 'bbbb', 'x', undefined) -}) - -test('BleManager properly handles errors while monitoring characteristic values', async () => { - const listener = jest.fn() - Native.BleModule.monitorCharacteristicForDevice = jest.fn().mockReturnValue(Promise.resolve(null)) - const subscription = bleManager.monitorCharacteristicForDevice('id', 'aaaa', 'bbbb', listener, 'x') - Native.BleModule.emit(Native.BleModule.ReadEvent, [nativeOperationCancelledError, { id: 'a', value: 'b' }, 'x']) - subscription.remove() - expect(listener.mock.calls.length).toBe(1) - expect(listener.mock.calls[0][0].message).toBe(BleErrorCodeMessage[BleErrorCode.OperationCancelled]) -}) - -test('BleManager properly requests the MTU', async () => { - bleManager.requestMTUForDevice('id', 99, 'trId') - expect(Native.BleModule.requestMTUForDevice).toBeCalledWith('id', 99, 'trId') -}) - -test('BleManager properly requests connection priority', async () => { - bleManager.requestConnectionPriorityForDevice('id', 2, 'trId') - expect(Native.BleModule.requestConnectionPriorityForDevice).toBeCalledWith('id', 2, 'trId') -}) - -test('BleManager properly reads descriptors value', async () => { - Native.BleModule.readDescriptorForDevice = jest - .fn() - .mockReturnValueOnce(Promise.resolve({ uuid: 'aaaa', value: '=AA' })) - const descriptor = await bleManager.readDescriptorForDevice( - 'id', - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'trans' - ) - expect(descriptor).toBeInstanceOf(Descriptor) - expect(descriptor.uuid).toBe('aaaa') - expect(descriptor.value).toBe('=AA') - expect(Native.BleModule.readDescriptorForDevice).toBeCalledWith( - 'id', - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'trans' - ) -}) - -test('BleManager properly writes descriptors value', async () => { - Native.BleModule.writeDescriptorForDevice = jest - .fn() - .mockReturnValueOnce(Promise.resolve({ uuid: 'aaaa', value: 'value' })) - const descriptor = await bleManager.writeDescriptorForDevice( - 'id', - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'value', - 'trans' - ) - expect(descriptor).toBeInstanceOf(Descriptor) - expect(descriptor.uuid).toBe('aaaa') - expect(descriptor.value).toBe('value') - expect(Native.BleModule.writeDescriptorForDevice).toBeCalledWith( - 'id', - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'value', - 'trans' - ) -}) diff --git a/__tests__/BleManager.test.ts b/__tests__/BleManager.test.ts new file mode 100644 index 000000000..dfcc76a25 --- /dev/null +++ b/__tests__/BleManager.test.ts @@ -0,0 +1,806 @@ +// __tests__/BleManager.test.ts + +// --------------------------------------------------------------------------- +// Type imports for mock handler signatures +// --------------------------------------------------------------------------- +import type { + ScanResult, + ConnectionStateEvent, + CharacteristicValueEvent, + RestoreStateEvent, + BondStateEvent, + ConnectionEvent +} from '../src/BleManager' + +// --------------------------------------------------------------------------- +// Native event payload types used only by L2CAP handlers +// --------------------------------------------------------------------------- +interface L2CAPDataEvent { + readonly channelId: number + readonly data: string +} + +interface L2CAPCloseEvent { + readonly channelId: number + readonly error: string | null +} + +interface StateChangeEvent { + readonly state: string +} + +// --------------------------------------------------------------------------- +// Mock setup — must happen before any imports that pull in react-native +// --------------------------------------------------------------------------- + +let scanResultHandlers: Array<(result: ScanResult) => void> = [] +let connectionStateHandlers: Array<(event: ConnectionStateEvent) => void> = [] +let charValueHandlers: Array<(event: CharacteristicValueEvent) => void> = [] +let stateChangeHandler: ((event: StateChangeEvent) => void) | null = null +let restoreStateHandler: ((event: RestoreStateEvent) => void) | null = null +let bondStateChangeHandler: ((event: BondStateEvent) => void) | null = null +let connectionEventHandler: ((event: ConnectionEvent) => void) | null = null +let l2capDataHandler: ((event: L2CAPDataEvent) => void) | null = null +let l2capCloseHandler: ((event: L2CAPCloseEvent) => void) | null = null + +// Convenience aliases for single-handler tests +const getScanResultHandler = () => scanResultHandlers[scanResultHandlers.length - 1] ?? null +const getConnectionStateHandler = (index = 0) => connectionStateHandlers[index] ?? null + +const mockNativeModule = { + createClient: jest.fn().mockResolvedValue(undefined), + destroyClient: jest.fn().mockResolvedValue(undefined), + state: jest.fn().mockResolvedValue('PoweredOn'), + startDeviceScan: jest.fn(), + stopDeviceScan: jest.fn().mockResolvedValue(undefined), + connectToDevice: jest.fn().mockResolvedValue({ + id: 'AA:BB', + name: 'Test', + rssi: -50, + mtu: 23, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + cancelDeviceConnection: jest.fn().mockResolvedValue({ + id: 'AA:BB', + name: 'Test', + rssi: -50, + mtu: 23, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + isDeviceConnected: jest.fn().mockResolvedValue(true), + discoverAllServicesAndCharacteristics: jest.fn().mockResolvedValue({ + id: 'AA:BB', + name: 'Test', + rssi: -50, + mtu: 23, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + readCharacteristic: jest.fn().mockResolvedValue({ + deviceId: 'AA:BB', + serviceUuid: 'svc', + uuid: 'char', + value: 'AQID', + isNotifying: false, + isIndicatable: false, + isReadable: true, + isWritableWithResponse: true, + isWritableWithoutResponse: false + }), + writeCharacteristic: jest.fn().mockResolvedValue({ + deviceId: 'AA:BB', + serviceUuid: 'svc', + uuid: 'char', + value: 'AQID', + isNotifying: false, + isIndicatable: false, + isReadable: true, + isWritableWithResponse: true, + isWritableWithoutResponse: false + }), + monitorCharacteristic: jest.fn(), + requestMtu: jest.fn().mockResolvedValue({ + id: 'AA:BB', + name: 'Test', + rssi: -50, + mtu: 517, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + requestConnectionPriority: jest.fn().mockResolvedValue({ + id: 'AA:BB', + name: 'Test', + rssi: -50, + mtu: 23, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + cancelTransaction: jest.fn().mockResolvedValue(undefined), + getMtu: jest.fn().mockResolvedValue(23), + requestPhy: jest.fn().mockResolvedValue({ deviceId: 'AA:BB', txPhy: 2, rxPhy: 2 }), + readPhy: jest.fn().mockResolvedValue({ deviceId: 'AA:BB', txPhy: 1, rxPhy: 1 }), + getBondedDevices: jest.fn().mockResolvedValue([]), + getAuthorizationStatus: jest.fn().mockResolvedValue('granted'), + openL2CAPChannel: jest.fn().mockResolvedValue({ channelId: 1, deviceId: 'AA:BB', psm: 128 }), + writeL2CAPChannel: jest.fn().mockResolvedValue(undefined), + closeL2CAPChannel: jest.fn().mockResolvedValue(undefined), + onScanResult: jest.fn(handler => { + scanResultHandlers.push(handler) + return { + remove: jest.fn(() => { + scanResultHandlers = scanResultHandlers.filter(h => h !== handler) + }) + } + }), + onConnectionStateChange: jest.fn(handler => { + connectionStateHandlers.push(handler) + return { + remove: jest.fn(() => { + connectionStateHandlers = connectionStateHandlers.filter(h => h !== handler) + }) + } + }), + onCharacteristicValueUpdate: jest.fn(handler => { + charValueHandlers.push(handler) + return { + remove: jest.fn(() => { + charValueHandlers = charValueHandlers.filter(h => h !== handler) + }) + } + }), + onStateChange: jest.fn(handler => { + stateChangeHandler = handler + return { + remove: jest.fn(() => { + stateChangeHandler = null + }) + } + }), + onError: jest.fn(() => ({ + remove: jest.fn() + })), + onRestoreState: jest.fn(handler => { + restoreStateHandler = handler + return { + remove: jest.fn(() => { + restoreStateHandler = null + }) + } + }), + onBondStateChange: jest.fn(handler => { + bondStateChangeHandler = handler + return { + remove: jest.fn(() => { + bondStateChangeHandler = null + }) + } + }), + onConnectionEvent: jest.fn(handler => { + connectionEventHandler = handler + return { + remove: jest.fn(() => { + connectionEventHandler = null + }) + } + }), + onL2CAPData: jest.fn(handler => { + l2capDataHandler = handler + return { + remove: jest.fn(() => { + l2capDataHandler = null + }) + } + }), + onL2CAPClose: jest.fn(handler => { + l2capCloseHandler = handler + return { + remove: jest.fn(() => { + l2capCloseHandler = null + }) + } + }) +} + +jest.mock('react-native', () => ({ + TurboModuleRegistry: { + get: jest.fn(() => mockNativeModule) + }, + Platform: { + OS: 'ios' + } +})) + +// --------------------------------------------------------------------------- +// Imports (after mock setup — jest.mock must be hoisted before module imports) +// --------------------------------------------------------------------------- + +// eslint-disable-next-line import/first +import { BleManager } from '../src/BleManager' +// eslint-disable-next-line import/first +import { BleError } from '../src/BleError' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function makeScanResult(id = 'AA:BB') { + return { id, name: 'Device', rssi: -60, serviceUuids: [], manufacturerData: null } +} + +function makeCharEvent(txId: string, deviceId = 'AA:BB', svc = 'svc', char = 'char') { + return { deviceId, serviceUuid: svc, characteristicUuid: char, value: 'AQID', transactionId: txId } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('BleManager', () => { + let manager: BleManager + + beforeEach(() => { + jest.clearAllMocks() + // Reset captured handlers + scanResultHandlers = [] + connectionStateHandlers = [] + charValueHandlers = [] + stateChangeHandler = null + restoreStateHandler = null + bondStateChangeHandler = null + connectionEventHandler = null + + // Use scanBatchIntervalMs: 0 for immediate delivery in tests + manager = new BleManager({ scanBatchIntervalMs: 0 }) + }) + + afterEach(async () => { + // Best-effort cleanup to avoid timer leaks + await manager.destroyClient().catch(() => {}) + }) + + // ------------------------------------------------------------------------- + + test('startDeviceScan calls native and routes scan results immediately with batchInterval 0', async () => { + const received: ScanResult[] = [] + manager.startDeviceScan(null, null, (_err, device) => { + if (device) received.push(device) + }) + + expect(mockNativeModule.startDeviceScan).toHaveBeenCalledWith(null, {}) + expect(mockNativeModule.onScanResult).toHaveBeenCalled() + expect(getScanResultHandler()).not.toBeNull() + + // Simulate native scan results arriving — immediate delivery (batchInterval: 0) + getScanResultHandler()!(makeScanResult('AA:BB')) + getScanResultHandler()!(makeScanResult('CC:DD')) + + expect(received).toHaveLength(2) + expect(received[0].id).toBe('AA:BB') + expect(received[1].id).toBe('CC:DD') + }) + + // ------------------------------------------------------------------------- + + test('stopDeviceScan cleans up batcher and subscription', async () => { + const received: ScanResult[] = [] + manager.startDeviceScan(null, null, (_err, device) => { + if (device) received.push(device) + }) + + const subscriptionRemoveSpy = jest.spyOn(mockNativeModule.onScanResult.mock.results[0].value, 'remove') + + await manager.stopDeviceScan() + + expect(mockNativeModule.stopDeviceScan).toHaveBeenCalled() + expect(subscriptionRemoveSpy).toHaveBeenCalled() + + // After stop, scan handler is removed — scanResultHandlers should be empty + expect(scanResultHandlers).toHaveLength(0) + expect(received).toHaveLength(0) + }) + + // ------------------------------------------------------------------------- + + test('dispose discards buffered events instead of flushing', async () => { + // Use a batched manager (non-zero interval) to test discard behavior + const batchedManager = new BleManager({ scanBatchIntervalMs: 5000 }) + const received: ScanResult[] = [] + batchedManager.startDeviceScan(null, null, (_err, device) => { + if (device) received.push(device) + }) + + // Push events — they get buffered because interval is 5000ms + getScanResultHandler()!(makeScanResult('AA:BB')) + getScanResultHandler()!(makeScanResult('CC:DD')) + + // Stop scan — dispose should discard, not flush + await batchedManager.stopDeviceScan() + + // Events should have been discarded + expect(received).toHaveLength(0) + + await batchedManager.destroyClient().catch(() => {}) + }) + + // ------------------------------------------------------------------------- + + test('monitorCharacteristicForDevice routes filtered events', async () => { + await manager.createClient() + + const received: CharacteristicValueEvent[] = [] + const sub = manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', (_err, event) => { + if (event) received.push(event) + }) + + expect(mockNativeModule.monitorCharacteristic).toHaveBeenCalled() + expect(mockNativeModule.onCharacteristicValueUpdate).toHaveBeenCalled() + + const txId = ( + mockNativeModule.monitorCharacteristic.mock.calls[0] as [string, string, string, string | null, string | null] + )[4] + expect(txId).toMatch(/^__ble_tx_\d+$/) + + const charValueHandler = charValueHandlers[charValueHandlers.length - 1]! + + // Matching event — should be routed + charValueHandler(makeCharEvent(txId, 'AA:BB', 'svc', 'char')) + + // Non-matching events — different device, service, char, or txId + charValueHandler(makeCharEvent(txId, 'XX:XX', 'svc', 'char')) + charValueHandler(makeCharEvent(txId, 'AA:BB', 'other-svc', 'char')) + charValueHandler(makeCharEvent(txId, 'AA:BB', 'svc', 'other-char')) + charValueHandler(makeCharEvent('wrong-tx', 'AA:BB', 'svc', 'char')) + + expect(received).toHaveLength(1) + expect(received[0].deviceId).toBe('AA:BB') + + sub.remove() + }) + + // ------------------------------------------------------------------------- + + test('monitor subscription.remove() cancels native transaction AND removes JS listener', async () => { + await manager.createClient() + + const sub = manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', jest.fn()) + + const txId = ( + mockNativeModule.monitorCharacteristic.mock.calls[0] as [string, string, string, string | null, string | null] + )[4] + + sub.remove() + + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId) + + // JS listener should be removed + const countAfter = mockNativeModule.cancelTransaction.mock.calls.length + expect(countAfter).toBeGreaterThanOrEqual(1) + + // Calling remove() again should be idempotent + sub.remove() + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledTimes(countAfter) + }) + + // ------------------------------------------------------------------------- + + test('duplicate transactionId cleans up existing monitor', async () => { + await manager.createClient() + + const listener1 = jest.fn() + const listener2 = jest.fn() + + manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', listener1, { + transactionId: 'dup-tx' + }) + + // The first subscription should have its listener registered + const firstSubRemove = mockNativeModule.onCharacteristicValueUpdate.mock.results[0].value.remove + + // Create another monitor with the same txId — should clean up the first + manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', listener2, { + transactionId: 'dup-tx' + }) + + // The first batcher should have been disposed and first sub removed + expect(firstSubRemove).toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('onDeviceDisconnected filters by deviceId', () => { + const callbackA = jest.fn() + const callbackB = jest.fn() + + manager.onDeviceDisconnected('AA:BB', callbackA) + manager.onDeviceDisconnected('CC:DD', callbackB) + + // Two separate native listeners are registered (one per subscription) + expect(connectionStateHandlers).toHaveLength(2) + + const disconnectEvent = { + deviceId: 'AA:BB', + state: 'disconnected', + errorCode: null, + errorMessage: null + } + + // Fire the event through all registered handlers (simulating native broadcast) + connectionStateHandlers.forEach(h => h(disconnectEvent)) + + // Only callbackA should fire — it matches 'AA:BB'; callbackB is for 'CC:DD' + expect(callbackA).toHaveBeenCalledTimes(1) + expect(callbackA).toHaveBeenCalledWith(null, expect.objectContaining({ deviceId: 'AA:BB' })) + expect(callbackB).not.toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('error events become BleError instances in disconnect callback with correct platform', () => { + const callback = jest.fn() + manager.onDeviceDisconnected('AA:BB', callback) + + const handler = getConnectionStateHandler(0)! + handler({ + deviceId: 'AA:BB', + state: 'disconnected', + errorCode: 2, + errorMessage: 'Connection failed' + }) + + expect(callback).toHaveBeenCalledTimes(1) + const [err, device] = callback.mock.calls[0] as [BleError | null, ConnectionStateEvent | null] + expect(err).toBeInstanceOf(BleError) + expect(err!.code).toBe(2) + expect(err!.message).toBe('Connection failed') + // Platform should come from Platform.OS, not hardcoded + expect(err!.platform).toBe('ios') + expect(device).not.toBeNull() + }) + + // ------------------------------------------------------------------------- + + test('onDeviceDisconnected ignores non-disconnected state events', () => { + const callback = jest.fn() + manager.onDeviceDisconnected('AA:BB', callback) + + const handler = getConnectionStateHandler(0)! + handler({ deviceId: 'AA:BB', state: 'connected', errorCode: null, errorMessage: null }) + handler({ deviceId: 'AA:BB', state: 'connecting', errorCode: null, errorMessage: null }) + + expect(callback).not.toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('onStateChange emits current state when emitCurrentState is true', async () => { + const callback = jest.fn() + const sub = manager.onStateChange(callback, true) + + // stateChangeHandler should be registered + expect(mockNativeModule.onStateChange).toHaveBeenCalled() + + // The initial state() call is async — wait for microtask queue + await Promise.resolve() + + expect(mockNativeModule.state).toHaveBeenCalled() + expect(callback).toHaveBeenCalledWith('PoweredOn') + + // Also responds to state change events + stateChangeHandler!({ state: 'PoweredOff' }) + expect(callback).toHaveBeenCalledWith('PoweredOff') + + sub.remove() + }) + + // ------------------------------------------------------------------------- + + test('onStateChange does NOT fetch current state when emitCurrentState is false', () => { + const callback = jest.fn() + manager.onStateChange(callback, false) + + expect(mockNativeModule.state).not.toHaveBeenCalled() + expect(callback).not.toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('onStateChange callback does not fire after remove()', async () => { + const callback = jest.fn() + const sub = manager.onStateChange(callback, true) + + // Remove immediately before the async state read resolves + sub.remove() + + // Wait for the async state() to resolve + await Promise.resolve() + await Promise.resolve() + + // callback should NOT have been called — the removed guard prevents it + expect(callback).not.toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('transactionId auto-generation uses __ble_tx_N pattern', async () => { + await manager.createClient() + + manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char1', jest.fn()) + manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char2', jest.fn()) + + const calls = mockNativeModule.monitorCharacteristic.mock.calls as [ + string, + string, + string, + string | null, + string | null + ][] + const txId1 = calls[0][4] as string + const txId2 = calls[1][4] as string + + expect(txId1).toMatch(/^__ble_tx_\d+$/) + expect(txId2).toMatch(/^__ble_tx_\d+$/) + expect(txId1).not.toBe(txId2) + + const n1 = parseInt(txId1.replace('__ble_tx_', ''), 10) + const n2 = parseInt(txId2.replace('__ble_tx_', ''), 10) + expect(n2).toBe(n1 + 1) + }) + + // ------------------------------------------------------------------------- + + test('connectToDevice passes options to native', async () => { + const options = { autoConnect: false, timeout: 5000, requestMtu: 512 } + const result = await manager.connectToDevice('AA:BB', options) + + expect(mockNativeModule.connectToDevice).toHaveBeenCalledWith('AA:BB', options) + expect(result.id).toBe('AA:BB') + }) + + // ------------------------------------------------------------------------- + + test('destroyClient cleans up all subscriptions and cancels active transactions', async () => { + await manager.createClient() + + // Start two monitors so we have active transactions + const sub1 = manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char1', jest.fn()) + const sub2 = manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char2', jest.fn()) + + const txId1 = ( + mockNativeModule.monitorCharacteristic.mock.calls[0] as [string, string, string, string | null, string | null] + )[4] + const txId2 = ( + mockNativeModule.monitorCharacteristic.mock.calls[1] as [string, string, string, string | null, string | null] + )[4] + + // Also start a scan + manager.startDeviceScan(null, null, jest.fn()) + + await manager.destroyClient() + + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId1) + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId2) + expect(mockNativeModule.destroyClient).toHaveBeenCalled() + + // Scan subscription should have been removed + expect(scanResultHandlers).toHaveLength(0) + + // Attempting to call sub1/sub2.remove() after destroy should not throw + expect(() => sub1.remove()).not.toThrow() + expect(() => sub2.remove()).not.toThrow() + }) + + // ------------------------------------------------------------------------- + + test('destroyClient cleans up consumer subscriptions', async () => { + await manager.createClient() + + const stateCallback = jest.fn() + const disconnectCallback = jest.fn() + + manager.onStateChange(stateCallback) + manager.onDeviceDisconnected('AA:BB', disconnectCallback) + + const stateSubRemove = mockNativeModule.onStateChange.mock.results[0].value.remove + const connSubRemove = mockNativeModule.onConnectionStateChange.mock.results[0].value.remove + + await manager.destroyClient() + + // After destroy, consumer subscriptions should have been removed + expect(stateSubRemove).toHaveBeenCalled() + expect(connSubRemove).toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('createClient called twice does not leak error subscription', async () => { + await manager.createClient() + + const firstErrorSub = mockNativeModule.onError.mock.results[0].value + const firstRemoveSpy = jest.spyOn(firstErrorSub, 'remove') + + await manager.createClient() + + // The first error subscription should have been removed + expect(firstRemoveSpy).toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + + test('monitorCharacteristicForDevice passes subscriptionType to native', async () => { + await manager.createClient() + + manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', jest.fn(), { + subscriptionType: 'indicate' + }) + + const call = mockNativeModule.monitorCharacteristic.mock.calls[0] as [ + string, + string, + string, + string | null, + string | null + ] + expect(call[3]).toBe('indicate') + }) + + // ------------------------------------------------------------------------- + + test('constructor accepts scanBatchIntervalMs option', () => { + const customManager = new BleManager({ scanBatchIntervalMs: 500 }) + // Just verify it constructs without error + expect(customManager).toBeInstanceOf(BleManager) + }) + + // ------------------------------------------------------------------------- + + test('getMtu delegates to native', async () => { + const mtu = await manager.getMtu('AA:BB') + expect(mockNativeModule.getMtu).toHaveBeenCalledWith('AA:BB') + expect(mtu).toBe(23) + }) + + // ------------------------------------------------------------------------- + + test('requestPhy delegates to native', async () => { + const result = await manager.requestPhy('AA:BB', 2, 2) + expect(mockNativeModule.requestPhy).toHaveBeenCalledWith('AA:BB', 2, 2) + expect(result.txPhy).toBe(2) + }) + + // ------------------------------------------------------------------------- + + test('readPhy delegates to native', async () => { + const result = await manager.readPhy('AA:BB') + expect(mockNativeModule.readPhy).toHaveBeenCalledWith('AA:BB') + expect(result.txPhy).toBe(1) + }) + + // ------------------------------------------------------------------------- + + test('getBondedDevices delegates to native', async () => { + const devices = await manager.getBondedDevices() + expect(mockNativeModule.getBondedDevices).toHaveBeenCalled() + expect(devices).toEqual([]) + }) + + // ------------------------------------------------------------------------- + + test('getAuthorizationStatus delegates to native', async () => { + const status = await manager.getAuthorizationStatus() + expect(mockNativeModule.getAuthorizationStatus).toHaveBeenCalled() + expect(status).toBe('granted') + }) + + // ------------------------------------------------------------------------- + + test('L2CAP channel methods delegate to native', async () => { + const channel = await manager.openL2CAPChannel('AA:BB', 128) + expect(mockNativeModule.openL2CAPChannel).toHaveBeenCalledWith('AA:BB', 128) + expect(channel.channelId).toBe(1) + + await manager.writeL2CAPChannel(1, 'AQID') + expect(mockNativeModule.writeL2CAPChannel).toHaveBeenCalledWith(1, 'AQID') + + await manager.closeL2CAPChannel(1) + expect(mockNativeModule.closeL2CAPChannel).toHaveBeenCalledWith(1) + }) + + // ------------------------------------------------------------------------- + + test('onRestoreState subscribes to native event and tracks subscription', () => { + const callback = jest.fn() + const sub = manager.onRestoreState(callback) + + expect(mockNativeModule.onRestoreState).toHaveBeenCalled() + + restoreStateHandler!({ devices: [] }) + expect(callback).toHaveBeenCalledWith({ devices: [] }) + + sub.remove() + }) + + // ------------------------------------------------------------------------- + + test('onBondStateChange subscribes to native event and tracks subscription', () => { + const callback = jest.fn() + const sub = manager.onBondStateChange(callback) + + expect(mockNativeModule.onBondStateChange).toHaveBeenCalled() + + bondStateChangeHandler!({ deviceId: 'AA:BB', bondState: 'bonded' }) + expect(callback).toHaveBeenCalledWith({ deviceId: 'AA:BB', bondState: 'bonded' }) + + sub.remove() + }) + + // ------------------------------------------------------------------------- + + test('onConnectionEvent subscribes to native event and tracks subscription', () => { + const callback = jest.fn() + const sub = manager.onConnectionEvent(callback) + + expect(mockNativeModule.onConnectionEvent).toHaveBeenCalled() + + connectionEventHandler!({ deviceId: 'AA:BB', connectionState: 'connected' }) + expect(callback).toHaveBeenCalledWith({ deviceId: 'AA:BB', connectionState: 'connected' }) + + sub.remove() + }) + + // ------------------------------------------------------------------------- + + test('monitorL2CAPChannel receives data events filtered by channelId', () => { + const listener = jest.fn() + const sub = manager.monitorL2CAPChannel(1, listener) + + expect(mockNativeModule.onL2CAPData).toHaveBeenCalled() + expect(mockNativeModule.onL2CAPClose).toHaveBeenCalled() + + // Data for our channel + l2capDataHandler!({ channelId: 1, data: 'SGVsbG8=' }) + expect(listener).toHaveBeenCalledWith(null, { channelId: 1, data: 'SGVsbG8=' }) + + // Data for a different channel — should be ignored + l2capDataHandler!({ channelId: 2, data: 'other' }) + expect(listener).toHaveBeenCalledTimes(1) + + sub.remove() + }) + + test('monitorL2CAPChannel calls listener with (null, null) on clean close', () => { + const listener = jest.fn() + manager.monitorL2CAPChannel(1, listener) + + // Clean close (no error) + l2capCloseHandler!({ channelId: 1, error: null }) + expect(listener).toHaveBeenCalledWith(null, null) + }) + + test('monitorL2CAPChannel calls listener with BleError on error close', () => { + const listener = jest.fn() + manager.monitorL2CAPChannel(1, listener) + + // Error close + l2capCloseHandler!({ channelId: 1, error: 'Connection lost' }) + expect(listener).toHaveBeenCalledTimes(1) + const [error, data] = listener.mock.calls[0] + expect(error).toBeInstanceOf(Error) + expect(error.message).toBe('Connection lost') + expect(data).toBeNull() + }) + + test('monitorL2CAPChannel ignores close events for other channels', () => { + const listener = jest.fn() + manager.monitorL2CAPChannel(1, listener) + + l2capCloseHandler!({ channelId: 2, error: 'Connection lost' }) + expect(listener).not.toHaveBeenCalled() + }) +}) diff --git a/__tests__/Characteristic.js b/__tests__/Characteristic.js deleted file mode 100644 index b74c4ecbf..000000000 --- a/__tests__/Characteristic.js +++ /dev/null @@ -1,47 +0,0 @@ -jest.mock('../src/BleManager') -const { BleManager } = require('../src/BleManager') -const { Characteristic } = require('../src/Characteristic') - -describe("Test if Characteristic is properly calling BleManager's utility function:", () => { - const bleManager = new BleManager() - const characteristic = new Characteristic( - { id: 'cId', uuid: 'uuid', serviceUUID: 'serviceUUID', deviceID: 'deviceId' }, - bleManager - ) - - test('descriptors', async () => { - await characteristic.descriptors() - expect(bleManager._descriptorsForCharacteristic).toBeCalledWith('cId') - }) - - test('read', async () => { - await characteristic.read('id') - expect(bleManager._readCharacteristic).toBeCalledWith('cId', 'id') - }) - - test('writeWithResponse', async () => { - await characteristic.writeWithResponse('value', 'id') - expect(bleManager._writeCharacteristicWithResponse).toBeCalledWith('cId', 'value', 'id') - }) - - test('writeWithoutResponse', async () => { - await characteristic.writeWithoutResponse('value', 'id') - expect(bleManager._writeCharacteristicWithoutResponse).toBeCalledWith('cId', 'value', 'id') - }) - - test('monitor', async () => { - const listener = jest.fn() - await characteristic.monitor(listener, 'id') - expect(bleManager._monitorCharacteristic).toBeCalledWith('cId', listener, 'id', undefined) - }) - - test('readDescriptor', async () => { - await characteristic.readDescriptor('uuid', 'transId') - expect(bleManager._readDescriptorForCharacteristic).toBeCalledWith('cId', 'uuid', 'transId') - }) - - test('writeDescriptor', async () => { - await characteristic.writeDescriptor('uuid', 'value', 'transId') - expect(bleManager._writeDescriptorForCharacteristic).toBeCalledWith('cId', 'uuid', 'value', 'transId') - }) -}) diff --git a/__tests__/Descriptor.js b/__tests__/Descriptor.js deleted file mode 100644 index 5c6fd24bb..000000000 --- a/__tests__/Descriptor.js +++ /dev/null @@ -1,27 +0,0 @@ -jest.mock('../src/BleManager') -const { BleManager } = require('../src/BleManager') -const { Descriptor } = require('../src/Descriptor') - -describe("Test if Descriptor is properly calling BleManager's utility function:", () => { - const bleManager = new BleManager() - const descriptor = new Descriptor( - { - id: 'dId', - uuid: 'uuid', - characteristicUUID: 'characteristricUUID', - serviceUUID: 'serviceUUID', - deviceID: 'deviceId' - }, - bleManager - ) - - test('read', async () => { - await descriptor.read('id') - expect(bleManager._readDescriptor).toBeCalledWith('dId', 'id') - }) - - test('write', async () => { - await descriptor.write('value', 'id') - expect(bleManager._writeDescriptor).toBeCalledWith('dId', 'value', 'id') - }) -}) diff --git a/__tests__/Device.js b/__tests__/Device.js deleted file mode 100644 index 67d4c5950..000000000 --- a/__tests__/Device.js +++ /dev/null @@ -1,116 +0,0 @@ -jest.mock('../src/BleManager') -const { BleManager } = require('../src/BleManager') -const { Device } = require('../src/Device') - -describe("Test if Device is properly calling BleManager's utility function:", () => { - const bleManager = new BleManager() - const device = new Device({ id: 'id' }, bleManager) - - test('readRSSI', async () => { - await device.readRSSI() - expect(bleManager.readRSSIForDevice).toBeCalledWith('id', undefined) - await device.readRSSI('transaction') - expect(bleManager.readRSSIForDevice).toBeCalledWith('id', 'transaction') - }) - - test('connect', async () => { - await device.connect({}) - expect(bleManager.connectToDevice).toBeCalledWith('id', {}) - }) - - test('cancelConnection', async () => { - await device.cancelConnection() - expect(bleManager.cancelDeviceConnection).toBeCalledWith('id') - }) - - test('isConnected', async () => { - await device.isConnected() - expect(bleManager.isDeviceConnected).toBeCalledWith('id') - }) - - test('onDisconnected', async () => { - const listener = jest.fn() - await device.onDisconnected(listener) - expect(bleManager.onDeviceDisconnected).toBeCalledWith('id', listener) - }) - - test('discoverAllServicesAndCharacteristics', async () => { - await device.discoverAllServicesAndCharacteristics('transaction') - expect(bleManager.discoverAllServicesAndCharacteristicsForDevice).toBeCalledWith('id', 'transaction') - }) - - test('services', async () => { - await device.services() - expect(bleManager.servicesForDevice).toBeCalledWith('id') - }) - - test('characteristicsForService', async () => { - await device.characteristicsForService('aaaa') - expect(bleManager.characteristicsForDevice).toBeCalledWith('id', 'aaaa') - }) - - test('descriptorsForService', async () => { - await device.descriptorsForService('serviceUUID', 'characteristicUUID') - expect(bleManager.descriptorsForDevice).toBeCalledWith('id', 'serviceUUID', 'characteristicUUID') - }) - - test('readCharacteristicForService', async () => { - await device.readCharacteristicForService('aaaa', 'bbbb', 'id') - expect(bleManager.readCharacteristicForDevice).toBeCalledWith('id', 'aaaa', 'bbbb', 'id') - }) - - test('writeCharacteristicWithResponseForService', async () => { - await device.writeCharacteristicWithResponseForService('aaaa', 'bbbb', 'value', 'id') - expect(bleManager.writeCharacteristicWithResponseForDevice).toBeCalledWith('id', 'aaaa', 'bbbb', 'value', 'id') - }) - - test('writeCharacteristicWithoutResponseForService', async () => { - await device.writeCharacteristicWithoutResponseForService('aaaa', 'bbbb', 'value', 'id') - expect(bleManager.writeCharacteristicWithoutResponseForDevice).toBeCalledWith('id', 'aaaa', 'bbbb', 'value', 'id') - }) - - test('monitorCharacteristicForService', async () => { - const listener = jest.fn() - await device.monitorCharacteristicForService('aaaa', 'bbbb', listener, 'id') - expect(bleManager.monitorCharacteristicForDevice).toBeCalledWith('id', 'aaaa', 'bbbb', listener, 'id', undefined) - }) - - test('readDescriptorForService', async () => { - await device.readDescriptorForService('serviceUUID', 'characteristicUUID', 'descriptorUUID', 'transactionId') - expect(bleManager.readDescriptorForDevice).toBeCalledWith( - 'id', - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'transactionId' - ) - }) - - test('writeDescriptorForService', async () => { - await device.writeDescriptorForService( - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'value', - 'transactionId' - ) - expect(bleManager.writeDescriptorForDevice).toBeCalledWith( - 'id', - 'serviceUUID', - 'characteristicUUID', - 'descriptorUUID', - 'value', - 'transactionId' - ) - }) - - test('BleManager properly requests the MTU', async () => { - await device.requestMTU(24, 'tid') - expect(bleManager.requestMTUForDevice).toBeCalledWith('id', 24, 'tid') - }) - - test('BleManager properly requests connection parameters', async () => { - await device.requestConnectionPriority(1, 'tid') - expect(bleManager.requestConnectionPriorityForDevice).toBeCalledWith('id', 1, 'tid') - }) -}) diff --git a/__tests__/EventBatcher.test.ts b/__tests__/EventBatcher.test.ts new file mode 100644 index 000000000..652fba2f4 --- /dev/null +++ b/__tests__/EventBatcher.test.ts @@ -0,0 +1,69 @@ +import { EventBatcher } from '../src/EventBatcher' + +test('immediate mode delivers events without delay', () => { + const received: number[] = [] + const batcher = new EventBatcher(0, 50, batch => { + received.push(...batch) + }) + batcher.push(1) + batcher.push(2) + expect(received).toEqual([1, 2]) + batcher.dispose() +}) + +test('batched mode collects events and delivers on interval', done => { + const received: number[][] = [] + const batcher = new EventBatcher(50, 50, batch => { + received.push([...batch]) + if (received.length === 1) { + expect(received[0]!).toEqual([1, 2, 3]) + batcher.dispose() + done() + } + }) + batcher.push(1) + batcher.push(2) + batcher.push(3) + // Events should not be delivered yet + expect(received).toEqual([]) +}) + +test('max batch size caps delivery', done => { + const received: number[][] = [] + const batcher = new EventBatcher(50, 3, batch => { + received.push([...batch]) + if (received.length === 1) { + expect(received[0]!.length).toBeLessThanOrEqual(3) + batcher.dispose() + done() + } + }) + for (let i = 0; i < 10; i += 1) batcher.push(i) +}) + +test('dispose discards buffered events instead of flushing', () => { + const received: number[] = [] + const batcher = new EventBatcher(5000, 50, batch => { + received.push(...batch) + }) + batcher.push(1) + batcher.push(2) + batcher.push(3) + + // Events are buffered (interval is 5000ms) + expect(received).toEqual([]) + + // Dispose should discard, not flush + batcher.dispose() + expect(received).toEqual([]) +}) + +test('push after dispose is ignored', () => { + const received: number[] = [] + const batcher = new EventBatcher(0, 50, batch => { + received.push(...batch) + }) + batcher.dispose() + batcher.push(1) + expect(received).toEqual([]) +}) diff --git a/__tests__/Service.js b/__tests__/Service.js deleted file mode 100644 index 6f1b914d2..000000000 --- a/__tests__/Service.js +++ /dev/null @@ -1,60 +0,0 @@ -jest.mock('../src/BleManager') -const { BleManager } = require('../src/BleManager') -const { Service } = require('../src/Service') - -describe("Test if Service is properly calling BleManager's utility function:", () => { - const bleManager = new BleManager() - const service = new Service({ id: 'serviceId', uuid: 'serviceUUID', deviceID: 'deviceId' }, bleManager) - - test('characteristics', async () => { - await service.characteristics() - expect(bleManager._characteristicsForService).toBeCalledWith('serviceId') - }) - - test('descriptorsForCharacteristic', async () => { - await service.descriptorsForCharacteristic('characteristicUUID') - expect(bleManager._descriptorsForService).toBeCalledWith('serviceId', 'characteristicUUID') - }) - - test('readCharacteristic', async () => { - await service.readCharacteristic('bbbb', 'id') - expect(bleManager._readCharacteristicForService).toBeCalledWith('serviceId', 'bbbb', 'id') - }) - - test('writeCharacteristicWithResponse', async () => { - await service.writeCharacteristicWithResponse('bbbb', 'value', 'id') - expect(bleManager._writeCharacteristicWithResponseForService).toBeCalledWith('serviceId', 'bbbb', 'value', 'id') - }) - - test('writeCharacteristicWithoutResponse', async () => { - await service.writeCharacteristicWithoutResponse('bbbb', 'value', 'id') - expect(bleManager._writeCharacteristicWithoutResponseForService).toBeCalledWith('serviceId', 'bbbb', 'value', 'id') - }) - - test('monitorCharacteristic', async () => { - const listener = jest.fn() - await service.monitorCharacteristic('bbbb', listener, 'id') - expect(bleManager._monitorCharacteristicForService).toBeCalledWith('serviceId', 'bbbb', listener, 'id', undefined) - }) - - test('readDescriptorForCharacteristic', async () => { - await service.readDescriptorForCharacteristic('characteristicUUID', 'descriptorUUID', 'transactionId') - expect(bleManager._readDescriptorForService).toBeCalledWith( - 'serviceId', - 'characteristicUUID', - 'descriptorUUID', - 'transactionId' - ) - }) - - test('writeDescriptorForCharacteristic', async () => { - await service.writeDescriptorForCharacteristic('characteristicUUID', 'descriptorUUID', 'value', 'transactionId') - expect(bleManager._writeDescriptorForService).toBeCalledWith( - 'serviceId', - 'characteristicUUID', - 'descriptorUUID', - 'value', - 'transactionId' - ) - }) -}) diff --git a/__tests__/Utils.js b/__tests__/Utils.js deleted file mode 100644 index 71994a148..000000000 --- a/__tests__/Utils.js +++ /dev/null @@ -1,72 +0,0 @@ -const EventEmitter = require('events') -import { fullUUID, fillStringWithArguments } from '../src/Utils' - -export class NativeEventEmitter extends EventEmitter { - constructor(module) { - super() - module.emit = this.emit.bind(this) - } - emit() { - return super.emit.apply(this, arguments) - } - addListener(eventName, listener) { - super.addListener(eventName, listener) - return { - remove: () => { - super.removeListener(eventName, listener) - } - } - } -} - -test('Mocked NativeEventEmitter allows to emit events directly by module', () => { - const module = { - emit: jest.fn() - } - const emitter = new NativeEventEmitter(module) - - const listener = jest.fn() - const listener2 = jest.fn() - - emitter.addListener('event', listener) - emitter.addListener('event2', listener2) - - module.emit('event', 'a') - module.emit('event2', 'b') - module.emit('event2', 'c') - - expect(listener.mock.calls).toEqual([['a']]) - expect(listener2.mock.calls).toEqual([['b'], ['c']]) -}) - -test('Mocked NativeEventEmitter allows to unsubscribe from events', () => { - const module = { - emit: jest.fn() - } - const emitter = new NativeEventEmitter(module) - const listener = jest.fn() - - module.emit('event', 'a') - const subscription = emitter.addListener('event', listener) - module.emit('event', 'b') - subscription.remove() - module.emit('event', 'c') - expect(listener.mock.calls).toEqual([['b']]) -}) - -test('fullUUID properly transforms 16bit UUID', () => { - expect(fullUUID('180A')).toBe('0000180a-0000-1000-8000-00805f9b34fb') -}) - -test('fullUUID properly transforms 32bit UUID', () => { - expect(fullUUID('180AffFF')).toBe('180affff-0000-1000-8000-00805f9b34fb') -}) - -test('fullUUID properly transforms 128bit UUID', () => { - expect(fullUUID('0000180A-0000-1000-8000-00805f9B34Fb')).toBe('0000180a-0000-1000-8000-00805f9b34fb') -}) - -test('string replacment based on object', () => { - expect(fillStringWithArguments('hello', {})).toBe('hello') - expect(fillStringWithArguments('My {id} is {a} or {b}', { a: 'OK', id: 'X' })).toBe('My X is OK or ?') -}) diff --git a/__tests__/protocol.test.ts b/__tests__/protocol.test.ts new file mode 100644 index 000000000..88e3c0a65 --- /dev/null +++ b/__tests__/protocol.test.ts @@ -0,0 +1,467 @@ +// __tests__/protocol.test.ts +// Tests for protocol-level concerns: Base64, DeviceInfo/CharacteristicInfo shapes, +// error event conversion, BleErrorCode distinctness, and public type constants. + +import { BleError, BleErrorCode } from '../src/BleError' +import { State, ConnectionState, ConnectionPriority } from '../src/types' + +// --------------------------------------------------------------------------- +// Base64 round-trip +// --------------------------------------------------------------------------- + +describe('Base64 encode/decode round-trip', () => { + // react-native-ble-plx uses base64 strings for characteristic values. + // The protocol contract is that btoa/atob (or equivalent) must round-trip. + + const cases: Array<[string, string]> = [ + ['hello', 'aGVsbG8='], + ['\x01\x02\x03', 'AQID'], + ['', ''], + ['\x00', 'AA=='], + ['\xFF\xFE\xFD', '//79'] + ] + + test.each(cases)('btoa(%p) === %p', (raw, expected) => { + // In Node / jsdom, btoa works on latin-1 byte strings + if (raw === '') { + expect(btoa(raw)).toBe('') + } else { + expect(btoa(raw)).toBe(expected) + } + }) + + test.each(cases)('atob(btoa(x)) round-trips', (raw, _b64) => { + if (raw === '') { + expect(atob(btoa(raw))).toBe(raw) + } else { + expect(atob(btoa(raw))).toBe(raw) + } + }) + + test('AQID decodes to three bytes 1,2,3', () => { + const decoded = atob('AQID') + expect(decoded.charCodeAt(0)).toBe(1) + expect(decoded.charCodeAt(1)).toBe(2) + expect(decoded.charCodeAt(2)).toBe(3) + }) + + test('null value field is preserved as null (no decode attempted)', () => { + // When native returns null for value, JS must not call atob(null) + const value: string | null = null + expect(value === null ? null : atob(value)).toBeNull() + }) +}) + +// --------------------------------------------------------------------------- +// DeviceInfo shape +// --------------------------------------------------------------------------- + +describe('DeviceInfo parsing', () => { + interface DeviceInfo { + id: string + name: string | null + rssi: number + mtu: number + isConnectable: boolean | null + serviceUuids: readonly string[] + manufacturerData: string | null + } + + function parseDeviceInfo(raw: Record): DeviceInfo { + return { + id: raw.id as string, + name: (raw.name as string | null) ?? null, + rssi: raw.rssi as number, + mtu: raw.mtu as number, + isConnectable: (raw.isConnectable as boolean | null) ?? null, + serviceUuids: (raw.serviceUuids as string[]) ?? [], + manufacturerData: (raw.manufacturerData as string | null) ?? null + } + } + + test('valid DeviceInfo parses all fields', () => { + const raw = { + id: 'AA:BB:CC:DD:EE:FF', + name: 'HeartSensor', + rssi: -65, + mtu: 247, + isConnectable: true, + serviceUuids: ['180D', '1800'], + manufacturerData: 'AQID' + } + const info = parseDeviceInfo(raw) + expect(info.id).toBe('AA:BB:CC:DD:EE:FF') + expect(info.name).toBe('HeartSensor') + expect(info.rssi).toBe(-65) + expect(info.mtu).toBe(247) + expect(info.isConnectable).toBe(true) + expect(info.serviceUuids).toEqual(['180D', '1800']) + expect(info.manufacturerData).toBe('AQID') + }) + + test('null fields handled correctly', () => { + const raw = { + id: 'AA:BB', + name: null, + rssi: -90, + mtu: 23, + isConnectable: null, + serviceUuids: [], + manufacturerData: null + } + const info = parseDeviceInfo(raw) + expect(info.name).toBeNull() + expect(info.isConnectable).toBeNull() + expect(info.manufacturerData).toBeNull() + expect(info.serviceUuids).toEqual([]) + }) + + test('missing optional fields default to null/empty', () => { + const raw = { id: 'AA:BB', rssi: -70, mtu: 23 } + const info = parseDeviceInfo(raw) + expect(info.name).toBeNull() + expect(info.isConnectable).toBeNull() + expect(info.manufacturerData).toBeNull() + expect(info.serviceUuids).toEqual([]) + }) +}) + +// --------------------------------------------------------------------------- +// CharacteristicInfo shape +// --------------------------------------------------------------------------- + +describe('CharacteristicInfo parsing', () => { + interface CharacteristicInfo { + deviceId: string + serviceUuid: string + uuid: string + value: string | null + isNotifying: boolean + isIndicatable: boolean + isReadable: boolean + isWritableWithResponse: boolean + isWritableWithoutResponse: boolean + } + + function parseCharInfo(raw: Record): CharacteristicInfo { + return { + deviceId: raw.deviceId as string, + serviceUuid: raw.serviceUuid as string, + uuid: raw.uuid as string, + value: (raw.value as string | null) ?? null, + isNotifying: Boolean(raw.isNotifying), + isIndicatable: Boolean(raw.isIndicatable), + isReadable: Boolean(raw.isReadable), + isWritableWithResponse: Boolean(raw.isWritableWithResponse), + isWritableWithoutResponse: Boolean(raw.isWritableWithoutResponse) + } + } + + test('fully populated characteristic info', () => { + const raw = { + deviceId: 'AA:BB', + serviceUuid: '180D', + uuid: '2A37', + value: 'AQID', + isNotifying: true, + isIndicatable: false, + isReadable: true, + isWritableWithResponse: false, + isWritableWithoutResponse: false + } + const info = parseCharInfo(raw) + expect(info.deviceId).toBe('AA:BB') + expect(info.serviceUuid).toBe('180D') + expect(info.uuid).toBe('2A37') + expect(info.value).toBe('AQID') + expect(info.isNotifying).toBe(true) + expect(info.isReadable).toBe(true) + }) + + test('null value field is preserved as null', () => { + const raw = { + deviceId: 'AA:BB', + serviceUuid: '180D', + uuid: '2A37', + value: null, + isNotifying: false, + isIndicatable: false, + isReadable: true, + isWritableWithResponse: true, + isWritableWithoutResponse: false + } + const info = parseCharInfo(raw) + expect(info.value).toBeNull() + }) + + test('all capability flags can be false', () => { + const raw = { + deviceId: 'AA:BB', + serviceUuid: 'svc', + uuid: 'char', + value: null, + isNotifying: false, + isIndicatable: false, + isReadable: false, + isWritableWithResponse: false, + isWritableWithoutResponse: false + } + const info = parseCharInfo(raw) + expect(info.isNotifying).toBe(false) + expect(info.isIndicatable).toBe(false) + expect(info.isReadable).toBe(false) + expect(info.isWritableWithResponse).toBe(false) + expect(info.isWritableWithoutResponse).toBe(false) + }) +}) + +// --------------------------------------------------------------------------- +// Error event conversion +// --------------------------------------------------------------------------- + +describe('BleError construction from native error events', () => { + function convertErrorEvent(raw: { + code: number + message: string + isRetryable: boolean + platform: string + deviceId?: string | null + serviceUuid?: string | null + characteristicUuid?: string | null + operation?: string | null + nativeDomain?: string | null + nativeCode?: number | null + gattStatus?: number | null + attErrorCode?: number | null + }): BleError { + return new BleError(raw) + } + + test('converts a permission denied error event correctly', () => { + const err = convertErrorEvent({ + code: BleErrorCode.BluetoothUnauthorized, + message: 'Bluetooth permission denied', + isRetryable: false, + platform: 'ios', + deviceId: null, + serviceUuid: null, + characteristicUuid: null, + operation: 'scan', + nativeDomain: 'CBErrorDomain', + nativeCode: 13, + gattStatus: null, + attErrorCode: null + }) + + expect(err).toBeInstanceOf(BleError) + expect(err.code).toBe(BleErrorCode.BluetoothUnauthorized) + expect(err.message).toBe('Bluetooth permission denied') + expect(err.isRetryable).toBe(false) + expect(err.platform).toBe('ios') + expect(err.deviceId).toBeUndefined() + expect(err.operation).toBe('scan') + expect(err.nativeDomain).toBe('CBErrorDomain') + expect(err.nativeCode).toBe(13) + expect(err.gattStatus).toBeUndefined() + }) + + test('converts a connection timeout error with device context', () => { + const err = convertErrorEvent({ + code: BleErrorCode.ConnectionTimeout, + message: 'Connection timeout', + isRetryable: true, + platform: 'android', + deviceId: 'AA:BB:CC:DD:EE:FF', + serviceUuid: null, + characteristicUuid: null, + operation: 'connect', + nativeDomain: null, + nativeCode: null, + gattStatus: 133, + attErrorCode: null + }) + + expect(err.code).toBe(BleErrorCode.ConnectionTimeout) + expect(err.isRetryable).toBe(true) + expect(err.platform).toBe('android') + expect(err.deviceId).toBe('AA:BB:CC:DD:EE:FF') + expect(err.gattStatus).toBe(133) + expect(err.nativeDomain).toBeUndefined() + }) + + test('converts a GATT characteristic write error with full context', () => { + const err = convertErrorEvent({ + code: BleErrorCode.CharacteristicWriteFailed, + message: 'Write failed', + isRetryable: false, + platform: 'android', + deviceId: 'AA:BB', + serviceUuid: '180D', + characteristicUuid: '2A37', + operation: 'write', + nativeDomain: null, + nativeCode: null, + gattStatus: 5, + attErrorCode: 3 + }) + + expect(err.code).toBe(BleErrorCode.CharacteristicWriteFailed) + expect(err.serviceUUID).toBe('180D') + expect(err.characteristicUUID).toBe('2A37') + expect(err.gattStatus).toBe(5) + expect(err.attErrorCode).toBe(3) + }) + + test('unknown event type does not crash — falls through to UnknownError code', () => { + // A future native event with an unknown code should still construct cleanly + const err = convertErrorEvent({ + code: 9999, + message: 'Future error type', + isRetryable: false, + platform: 'ios' + }) + + expect(err).toBeInstanceOf(BleError) + // code is stored as-is (cast to BleErrorCode type) + expect(err.code).toBe(9999) + expect(err.message).toBe('Future error type') + }) + + test('all null optional fields result in undefined properties (not null)', () => { + const err = convertErrorEvent({ + code: BleErrorCode.UnknownError, + message: 'Unknown', + isRetryable: false, + platform: 'ios', + deviceId: null, + serviceUuid: null, + characteristicUuid: null, + operation: null, + nativeDomain: null, + nativeCode: null, + gattStatus: null, + attErrorCode: null + }) + + // BleError constructor maps null → undefined + expect(err.deviceId).toBeUndefined() + expect(err.serviceUUID).toBeUndefined() + expect(err.characteristicUUID).toBeUndefined() + expect(err.operation).toBeUndefined() + expect(err.nativeDomain).toBeUndefined() + expect(err.nativeCode).toBeUndefined() + expect(err.gattStatus).toBeUndefined() + expect(err.attErrorCode).toBeUndefined() + }) +}) + +// --------------------------------------------------------------------------- +// BleErrorCode — all values are distinct numbers +// --------------------------------------------------------------------------- + +describe('BleErrorCode', () => { + test('all BleErrorCode values are distinct numbers', () => { + const values = Object.values(BleErrorCode) + const unique = new Set(values) + expect(unique.size).toBe(values.length) + values.forEach(v => expect(typeof v).toBe('number')) + }) + + test('spot-check specific BleErrorCode numeric values', () => { + expect(BleErrorCode.DeviceNotFound).toBe(0) + expect(BleErrorCode.DeviceDisconnected).toBe(1) + expect(BleErrorCode.ConnectionFailed).toBe(2) + expect(BleErrorCode.ConnectionTimeout).toBe(3) + expect(BleErrorCode.OperationCancelled).toBe(100) + expect(BleErrorCode.CharacteristicNotFound).toBe(200) + expect(BleErrorCode.BluetoothUnauthorized).toBe(300) + expect(BleErrorCode.ManagerNotInitialized).toBe(400) + expect(BleErrorCode.BondingFailed).toBe(500) + expect(BleErrorCode.L2CAPChannelFailed).toBe(600) + expect(BleErrorCode.PhyNegotiationFailed).toBe(700) + expect(BleErrorCode.ScanFailed).toBe(800) + expect(BleErrorCode.UnknownError).toBe(999) + }) + + test('BleError preserves all context fields', () => { + const err = new BleError({ + code: BleErrorCode.CharacteristicReadFailed, + message: 'Read failed: ATT error 2', + isRetryable: false, + platform: 'android', + deviceId: 'DE:AD:BE:EF:00:01', + serviceUuid: '0000180d-0000-1000-8000-00805f9b34fb', + characteristicUuid: '00002a37-0000-1000-8000-00805f9b34fb', + operation: 'read', + nativeDomain: null, + nativeCode: null, + gattStatus: 2, + attErrorCode: 2 + }) + + expect(err.code).toBe(BleErrorCode.CharacteristicReadFailed) + expect(err.message).toBe('Read failed: ATT error 2') + expect(err.isRetryable).toBe(false) + expect(err.platform).toBe('android') + expect(err.deviceId).toBe('DE:AD:BE:EF:00:01') + expect(err.serviceUUID).toBe('0000180d-0000-1000-8000-00805f9b34fb') + expect(err.characteristicUUID).toBe('00002a37-0000-1000-8000-00805f9b34fb') + expect(err.operation).toBe('read') + expect(err.gattStatus).toBe(2) + expect(err.attErrorCode).toBe(2) + expect(err).toBeInstanceOf(Error) + expect(err.name).toBe('BleError') + }) +}) + +// --------------------------------------------------------------------------- +// State / ConnectionState / ConnectionPriority const objects +// --------------------------------------------------------------------------- + +describe('State const object', () => { + test('has all required string values', () => { + expect(State.Unknown).toBe('Unknown') + expect(State.Resetting).toBe('Resetting') + expect(State.Unsupported).toBe('Unsupported') + expect(State.Unauthorized).toBe('Unauthorized') + expect(State.PoweredOff).toBe('PoweredOff') + expect(State.PoweredOn).toBe('PoweredOn') + }) + + test('all State values are distinct strings', () => { + const values = Object.values(State) + const unique = new Set(values) + expect(unique.size).toBe(values.length) + values.forEach(v => expect(typeof v).toBe('string')) + }) +}) + +describe('ConnectionState const object', () => { + test('has all required string values', () => { + expect(ConnectionState.Disconnected).toBe('disconnected') + expect(ConnectionState.Connecting).toBe('connecting') + expect(ConnectionState.Connected).toBe('connected') + expect(ConnectionState.Disconnecting).toBe('disconnecting') + }) + + test('all ConnectionState values are distinct strings', () => { + const values = Object.values(ConnectionState) + const unique = new Set(values) + expect(unique.size).toBe(values.length) + }) +}) + +describe('ConnectionPriority const object', () => { + test('has correct numeric values', () => { + expect(ConnectionPriority.Balanced).toBe(0) + expect(ConnectionPriority.High).toBe(1) + expect(ConnectionPriority.LowPower).toBe(2) + }) + + test('all ConnectionPriority values are distinct numbers', () => { + const values = Object.values(ConnectionPriority) + const unique = new Set(values) + expect(unique.size).toBe(values.length) + values.forEach(v => expect(typeof v).toBe('number')) + }) +}) diff --git a/android/build.gradle b/android/build.gradle index 4f1fd5d1e..92a136b66 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,21 +6,13 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:8.8.0" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${project.properties["BlePlx_kotlinVersion"]}" } } -def isNewArchitectureEnabled() { - return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" -} - apply plugin: "com.android.library" - - -def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') } - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} +apply plugin: "kotlin-android" +apply plugin: "com.facebook.react" def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["BlePlx_" + name] @@ -30,27 +22,13 @@ def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["BlePlx_" + name]).toInteger() } -def supportsNamespace() { - def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') - def major = parsed[0].toInteger() - def minor = parsed[1].toInteger() - - // Namespace support was added in 7.3.0 - if (major == 7 && minor >= 3) { - return true - } - - return major >= 8 -} - android { - if (supportsNamespace()) { - namespace "com.bleplx" + namespace "com.bleplx" - sourceSets { - main { - manifest.srcFile "src/main/AndroidManifestNew.xml" - } + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + java.srcDirs += "src/main/kotlin" } } @@ -59,8 +37,8 @@ android { defaultConfig { minSdkVersion getExtOrIntegerDefault("minSdkVersion") targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") - buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() } + buildTypes { release { minifyEnabled false @@ -72,10 +50,13 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } } repositories { @@ -83,20 +64,24 @@ repositories { google() } - dependencies { - // For < 0.71, this will be from the local maven repo - // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" - implementation 'io.reactivex.rxjava2:rxjava:2.2.17' - implementation "com.polidea.rxandroidble2:rxandroidble:1.17.2" + implementation 'com.facebook.react:react-android' + implementation 'no.nordicsemi.android:ble:2.11.0' + implementation 'no.nordicsemi.android:ble-ktx:2.11.0' + implementation 'no.nordicsemi.android.support.v18:scanner:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' + + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'io.mockk:mockk:1.13.8' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0' } -if (isNewArchitectureEnabled()) { - react { - jsRootDir = file("../src/") - libraryName = "BlePlx" - codegenJavaPackageName = "com.bleplx" - } +react { + jsRootDir = file("../src/") + libraryName = "BlePlx" + codegenJavaPackageName = "com.bleplx" +} + +tasks.withType(Test).configureEach { + useJUnitPlatform() } diff --git a/android/src/main/AndroidManifestNew.xml b/android/src/main/AndroidManifestNew.xml index 0bfa73ba6..0af9cc4d5 100644 --- a/android/src/main/AndroidManifestNew.xml +++ b/android/src/main/AndroidManifestNew.xml @@ -1 +1,19 @@ - + + + + + + + + + diff --git a/android/src/main/java/com/bleplx/BlePlxModule.java b/android/src/main/java/com/bleplx/BlePlxModule.java deleted file mode 100644 index d4ef9c5eb..000000000 --- a/android/src/main/java/com/bleplx/BlePlxModule.java +++ /dev/null @@ -1,1123 +0,0 @@ -package com.bleplx; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bleplx.adapter.BleAdapter; -import com.bleplx.adapter.BleAdapterFactory; -import com.bleplx.adapter.Characteristic; -import com.bleplx.adapter.ConnectionOptions; -import com.bleplx.adapter.ConnectionState; -import com.bleplx.adapter.Descriptor; -import com.bleplx.adapter.Device; -import com.bleplx.adapter.OnErrorCallback; -import com.bleplx.adapter.OnEventCallback; -import com.bleplx.adapter.OnSuccessCallback; -import com.bleplx.adapter.RefreshGattMoment; -import com.bleplx.adapter.ScanResult; -import com.bleplx.adapter.Service; -import com.bleplx.adapter.errors.BleError; -import com.bleplx.adapter.errors.BleErrorCode; -import com.bleplx.converter.BleErrorToJsObjectConverter; -import com.bleplx.converter.CharacteristicToJsObjectConverter; -import com.bleplx.converter.DescriptorToJsObjectConverter; -import com.bleplx.converter.DeviceToJsObjectConverter; -import com.bleplx.converter.ScanResultToJsObjectConverter; -import com.bleplx.converter.ServiceToJsObjectConverter; -import com.bleplx.utils.ErrorDefaults; -import com.bleplx.utils.ReadableArrayConverter; -import com.bleplx.utils.SafePromise; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.polidea.rxandroidble2.internal.RxBleLog; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.reactivex.exceptions.UndeliverableException; -import io.reactivex.plugins.RxJavaPlugins; - -@ReactModule(name = BlePlxModule.NAME) -public class BlePlxModule extends ReactContextBaseJavaModule { - public static final String NAME = "BlePlx"; - private final ReactApplicationContext reactContext; - - public BlePlxModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - RxJavaPlugins.setErrorHandler(throwable -> { - if (throwable instanceof UndeliverableException) { - RxBleLog.e("Handle all unhandled exceptions from RxJava: " + throwable.getMessage()); - } else { - Thread currentThread = Thread.currentThread(); - Thread.UncaughtExceptionHandler errorHandler = currentThread.getUncaughtExceptionHandler(); - if (errorHandler != null) { - errorHandler.uncaughtException(currentThread, throwable); - } - } - }); - } - - @Override - @NonNull - public String getName() { - return NAME; - } - - - // Value converters - private final BleErrorToJsObjectConverter errorConverter = new BleErrorToJsObjectConverter(); - private final ScanResultToJsObjectConverter scanResultConverter = new ScanResultToJsObjectConverter(); - private final DeviceToJsObjectConverter deviceConverter = new DeviceToJsObjectConverter(); - private final CharacteristicToJsObjectConverter characteristicConverter = new CharacteristicToJsObjectConverter(); - private final DescriptorToJsObjectConverter descriptorConverter = new DescriptorToJsObjectConverter(); - private final ServiceToJsObjectConverter serviceConverter = new ServiceToJsObjectConverter(); - - private BleAdapter bleAdapter; - - @Override - public Map getConstants() { - final Map constants = new HashMap<>(); - for (Event event : Event.values()) { - constants.put(event.name, event.name); - } - return constants; - } - - // Lifecycle ----------------------------------------------------------------------------------- - - @ReactMethod - public void createClient(String restoreStateIdentifier) { - bleAdapter = BleAdapterFactory.getNewAdapter(reactContext); - bleAdapter.createClient(restoreStateIdentifier, - new OnEventCallback() { - @Override - public void onEvent(String state) { - sendEvent(Event.StateChangeEvent, state); - } - }, new OnEventCallback() { - @Override - public void onEvent(Integer data) { - sendEvent(Event.RestoreStateEvent, null); - } - }); - } - - @ReactMethod - public void destroyClient(final Promise promise) { - if (!this.isRequestPossibleHandler("destroyClient", promise)) { - return; - } - - bleAdapter.destroyClient(); - bleAdapter = null; - promise.resolve(null); - } - - // Mark: Common -------------------------------------------------------------------------------- - - @ReactMethod - public void cancelTransaction(String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("cancelTransaction", promise)) { - return; - } - bleAdapter.cancelTransaction(transactionId); - promise.resolve(null); - } - - @ReactMethod - public void setLogLevel(String logLevel, final Promise promise) { - if (!this.isRequestPossibleHandler("setLogLevel", promise)) { - return; - } - bleAdapter.setLogLevel(logLevel); - promise.resolve(bleAdapter.getLogLevel()); - } - - @ReactMethod - public void logLevel(final Promise promise) { - if (!this.isRequestPossibleHandler("logLevel", promise)) { - return; - } - promise.resolve(bleAdapter.getLogLevel()); - } - - // Mark: Monitoring state ---------------------------------------------------------------------- - - @ReactMethod - public void enable(final String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("enable", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.enable(transactionId, new OnSuccessCallback() { - @Override - public void onSuccess(Void data) { - safePromise.resolve(null); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void disable(final String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("disable", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.disable(transactionId, new OnSuccessCallback() { - @Override - public void onSuccess(Void data) { - safePromise.resolve(null); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void state(final Promise promise) { - if (!this.isRequestPossibleHandler("state", promise)) { - return; - } - promise.resolve(bleAdapter.getCurrentState()); - } - - // Mark: Scanning ------------------------------------------------------------------------------ - - @ReactMethod - public void startDeviceScan(@Nullable ReadableArray filteredUUIDs, @Nullable ReadableMap options, final Promise promise) { - if (!this.isRequestPossibleHandler("startDeviceScan", promise)) { - return; - } - final int DEFAULT_SCAN_MODE_LOW_POWER = 0; - final int DEFAULT_CALLBACK_TYPE_ALL_MATCHES = 1; - - int scanMode = DEFAULT_SCAN_MODE_LOW_POWER; - int callbackType = DEFAULT_CALLBACK_TYPE_ALL_MATCHES; - boolean legacyScan = true; - - if (options != null) { - if (options.hasKey("scanMode") && options.getType("scanMode") == ReadableType.Number) { - scanMode = options.getInt("scanMode"); - } - if (options.hasKey("callbackType") && options.getType("callbackType") == ReadableType.Number) { - callbackType = options.getInt("callbackType"); - } - if (options.hasKey("legacyScan") && options.getType("legacyScan") == ReadableType.Boolean) { - legacyScan = options.getBoolean("legacyScan"); - } - } - - bleAdapter.startDeviceScan( - filteredUUIDs != null ? ReadableArrayConverter.toStringArray(filteredUUIDs) : null, - scanMode, callbackType, legacyScan, - new OnEventCallback() { - @Override - public void onEvent(ScanResult data) { - sendEvent(Event.ScanEvent, scanResultConverter.toJSCallback(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - sendEvent(Event.ScanEvent, errorConverter.toJSCallback(error)); - } - }); - - promise.resolve(null); - } - - @ReactMethod - public void stopDeviceScan(final Promise promise) { - if (!this.isRequestPossibleHandler("stopDeviceScan", promise)) { - return; - } - bleAdapter.stopDeviceScan(); - promise.resolve(null); - } - - // Mark: Device management --------------------------------------------------------------------- - - @ReactMethod - public void devices(final ReadableArray deviceIdentifiers, final Promise promise) { - if (!this.isRequestPossibleHandler("devices", promise)) { - return; - } - bleAdapter.getKnownDevices(ReadableArrayConverter.toStringArray(deviceIdentifiers), - new OnSuccessCallback() { - @Override - public void onSuccess(Device[] data) { - WritableArray jsDevices = Arguments.createArray(); - for (Device device : data) { - jsDevices.pushMap(deviceConverter.toJSObject(device)); - } - promise.resolve(jsDevices); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - rejectWithBleError(promise, error); - } - }); - } - - @ReactMethod - public void connectedDevices(final ReadableArray serviceUUIDs, final Promise promise) { - if (!this.isRequestPossibleHandler("connectedDevices", promise)) { - return; - } - bleAdapter.getConnectedDevices(ReadableArrayConverter.toStringArray(serviceUUIDs), - new OnSuccessCallback() { - @Override - public void onSuccess(Device[] data) { - final WritableArray writableArray = Arguments.createArray(); - for (Device device : data) { - writableArray.pushMap(deviceConverter.toJSObject(device)); - } - promise.resolve(writableArray); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - rejectWithBleError(promise, error); - } - }); - } - - // Mark: Device operations --------------------------------------------------------------------- - - @ReactMethod - public void requestConnectionPriorityForDevice(final String deviceId, int connectionPriority, final String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("requestConnectionPriorityForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.requestConnectionPriorityForDevice(deviceId, connectionPriority, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Device data) { - safePromise.resolve(deviceConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void requestMTUForDevice(final String deviceId, int mtu, final String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("requestMTUForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.requestMTUForDevice(deviceId, mtu, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Device data) { - safePromise.resolve(deviceConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void readRSSIForDevice(final String deviceId, final String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("readRSSIForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.readRSSIForDevice(deviceId, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Device data) { - safePromise.resolve(deviceConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void connectToDevice(final String deviceId, @Nullable ReadableMap options, final Promise promise) { - if (!this.isRequestPossibleHandler("connectToDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - - boolean autoConnect = false; - int requestMtu = 0; - RefreshGattMoment refreshGattMoment = null; - Integer timeout = null; - int connectionPriority = 0; // CONNECTION_PRIORITY_BALANCED - - if (options != null) { - if (options.hasKey("autoConnect") && options.getType("autoConnect") == ReadableType.Boolean) { - autoConnect = options.getBoolean("autoConnect"); - } - if (options.hasKey("requestMTU") && options.getType("requestMTU") == ReadableType.Number) { - requestMtu = options.getInt("requestMTU"); - } - if (options.hasKey("refreshGatt") && options.getType("refreshGatt") == ReadableType.String) { - refreshGattMoment = RefreshGattMoment.getByName(options.getString("refreshGatt")); - } - if (options.hasKey("timeout") && options.getType("timeout") == ReadableType.Number) { - timeout = options.getInt("timeout"); - } - if (options.hasKey("connectionPriority") && options.getType("connectionPriority") == ReadableType.Number) { - connectionPriority = options.getInt("connectionPriority"); - } - } - bleAdapter.connectToDevice( - deviceId, - new ConnectionOptions(autoConnect, - requestMtu, - refreshGattMoment, - timeout != null ? timeout.longValue() : null, - connectionPriority), - new OnSuccessCallback() { - @Override - public void onSuccess(Device data) { - safePromise.resolve(deviceConverter.toJSObject(data)); - } - }, - new OnEventCallback() { - @Override - public void onEvent(ConnectionState connectionState) { - if (connectionState == ConnectionState.DISCONNECTED) { - WritableArray event = Arguments.createArray(); - event.pushNull(); - WritableMap device = Arguments.createMap(); - device.putString("id", deviceId); - event.pushMap(device); - sendEvent(Event.DisconnectionEvent, event); - } - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void cancelDeviceConnection(String deviceId, final Promise promise) { - if (!this.isRequestPossibleHandler("cancelDeviceConnection", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.cancelDeviceConnection(deviceId, - new OnSuccessCallback() { - @Override - public void onSuccess(Device data) { - safePromise.resolve(deviceConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void isDeviceConnected(String deviceId, final Promise promise) { - if (!this.isRequestPossibleHandler("isDeviceConnected", promise)) { - return; - } - bleAdapter.isDeviceConnected(deviceId, - new OnSuccessCallback() { - @Override - public void onSuccess(Boolean isConnected) { - promise.resolve(isConnected); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - rejectWithBleError(promise, error); - } - }); - } - - // Mark: Discovery ----------------------------------------------------------------------------- - - @ReactMethod - public void discoverAllServicesAndCharacteristicsForDevice(String deviceId, final String transactionId, final Promise promise) { - if (!this.isRequestPossibleHandler("discoverAllServicesAndCharacteristicsForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.discoverAllServicesAndCharacteristicsForDevice(deviceId, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Device data) { - safePromise.resolve(deviceConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - // Mark: Service and characteristic getters ---------------------------------------------------- - - @ReactMethod - public void servicesForDevice(final String deviceId, final Promise promise) { - if (!this.isRequestPossibleHandler("servicesForDevice", promise)) { - return; - } - try { - List services = bleAdapter.getServicesForDevice(deviceId); - WritableArray jsArray = Arguments.createArray(); - for (Service service : services) { - jsArray.pushMap(serviceConverter.toJSObject(service)); - } - promise.resolve(jsArray); - } catch (BleError error) { - rejectWithBleError(promise, error); - } - - } - - @ReactMethod - public void characteristicsForDevice(final String deviceId, - final String serviceUUID, - final Promise promise) { - if (!this.isRequestPossibleHandler("characteristicsForDevice", promise)) { - return; - } - try { - List characteristics = bleAdapter.getCharacteristicsForDevice(deviceId, serviceUUID); - - WritableArray jsCharacteristics = Arguments.createArray(); - for (Characteristic characteristic : characteristics) { - jsCharacteristics.pushMap(characteristicConverter.toJSObject(characteristic)); - } - promise.resolve(jsCharacteristics); - } catch (BleError error) { - rejectWithBleError(promise, error); - } - } - - @ReactMethod - public void characteristicsForService(final int serviceIdentifier, final Promise promise) { - if (!this.isRequestPossibleHandler("characteristicsForService", promise)) { - return; - } - try { - List characteristics = bleAdapter.getCharacteristicsForService(serviceIdentifier); - WritableArray jsCharacteristics = Arguments.createArray(); - for (Characteristic characteristic : characteristics) { - jsCharacteristics.pushMap(characteristicConverter.toJSObject(characteristic)); - } - promise.resolve(jsCharacteristics); - } catch (BleError error) { - rejectWithBleError(promise, error); - } - } - - @ReactMethod - public void descriptorsForDevice(final String deviceIdentifier, - final String serviceUUID, - final String characteristicUUID, - final Promise promise) { - if (!this.isRequestPossibleHandler("descriptorsForDevice", promise)) { - return; - } - try { - List descriptors = bleAdapter.descriptorsForDevice(deviceIdentifier, serviceUUID, characteristicUUID); - WritableArray jsDescriptors = Arguments.createArray(); - for (Descriptor descriptor : descriptors) { - jsDescriptors.pushMap(descriptorConverter.toJSObject(descriptor)); - } - promise.resolve(jsDescriptors); - } catch (BleError error) { - rejectWithBleError(promise, error); - } - } - - @ReactMethod - public void descriptorsForService(final int serviceIdentifier, - final String characteristicUUID, - final Promise promise) { - if (!this.isRequestPossibleHandler("descriptorsForService", promise)) { - return; - } - try { - List descriptors = bleAdapter.descriptorsForService(serviceIdentifier, characteristicUUID); - WritableArray jsDescriptors = Arguments.createArray(); - for (Descriptor descriptor : descriptors) { - jsDescriptors.pushMap(descriptorConverter.toJSObject(descriptor)); - } - promise.resolve(jsDescriptors); - } catch (BleError error) { - rejectWithBleError(promise, error); - } - } - - @ReactMethod - public void descriptorsForCharacteristic(final int characteristicIdentifier, - final Promise promise) { - if (!this.isRequestPossibleHandler("descriptorsForCharacteristic", promise)) { - return; - } - try { - List descriptors = bleAdapter.descriptorsForCharacteristic(characteristicIdentifier); - WritableArray jsDescriptors = Arguments.createArray(); - for (Descriptor descriptor : descriptors) { - jsDescriptors.pushMap(descriptorConverter.toJSObject(descriptor)); - } - promise.resolve(jsDescriptors); - } catch (BleError error) { - rejectWithBleError(promise, error); - } - } - - // Mark: Characteristics operations ------------------------------------------------------------ - - @ReactMethod - public void writeCharacteristicForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String valueBase64, - final Boolean response, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeCharacteristicForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - - bleAdapter.writeCharacteristicForDevice( - deviceId, serviceUUID, characteristicUUID, valueBase64, response, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Characteristic data) { - safePromise.resolve(characteristicConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void writeCharacteristicForService(final int serviceIdentifier, - final String characteristicUUID, - final String valueBase64, - final Boolean response, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeCharacteristicForService", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.writeCharacteristicForService( - serviceIdentifier, characteristicUUID, valueBase64, response, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Characteristic data) { - safePromise.resolve(characteristicConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void writeCharacteristic(final int characteristicIdentifier, - final String valueBase64, - final Boolean response, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeCharacteristic", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - - bleAdapter.writeCharacteristic(characteristicIdentifier, valueBase64, response, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Characteristic data) { - safePromise.resolve(characteristicConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - }); - } - - @ReactMethod - public void readCharacteristicForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readCharacteristicForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - - bleAdapter.readCharacteristicForDevice( - deviceId, serviceUUID, characteristicUUID, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Characteristic data) { - safePromise.resolve(characteristicConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void readCharacteristicForService(final int serviceIdentifier, - final String characteristicUUID, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readCharacteristicForService", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - - bleAdapter.readCharacteristicForService( - serviceIdentifier, characteristicUUID, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Characteristic data) { - safePromise.resolve(characteristicConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void readCharacteristic(final int characteristicIdentifier, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readCharacteristic", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - - bleAdapter.readCharacteristic( - characteristicIdentifier, transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Characteristic data) { - safePromise.resolve(characteristicConverter.toJSObject(data)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void monitorCharacteristicForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String transactionId, - final String subscriptionType, - final Promise promise) { - if (!this.isRequestPossibleHandler("monitorCharacteristicForDevice", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.monitorCharacteristicForDevice( - deviceId, serviceUUID, characteristicUUID, transactionId, subscriptionType, - new OnEventCallback() { - @Override - public void onEvent(Characteristic data) { - WritableArray jsResult = Arguments.createArray(); - jsResult.pushNull(); - jsResult.pushMap(characteristicConverter.toJSObject(data)); - jsResult.pushString(transactionId); - sendEvent(Event.ReadEvent, jsResult); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void monitorCharacteristicForService(final int serviceIdentifier, - final String characteristicUUID, - final String transactionId, - final String subscriptionType, - final Promise promise) { - if (!this.isRequestPossibleHandler("monitorCharacteristicForService", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - bleAdapter.monitorCharacteristicForService( - serviceIdentifier, characteristicUUID, transactionId, subscriptionType, - new OnEventCallback() { - @Override - public void onEvent(Characteristic data) { - WritableArray jsResult = Arguments.createArray(); - jsResult.pushNull(); - jsResult.pushMap(characteristicConverter.toJSObject(data)); - jsResult.pushString(transactionId); - sendEvent(Event.ReadEvent, jsResult); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void monitorCharacteristic(final int characteristicIdentifier, - final String transactionId, - final String subscriptionType, - final Promise promise) { - if (!this.isRequestPossibleHandler("monitorCharacteristic", promise)) { - return; - } - final SafePromise safePromise = new SafePromise(promise); - //TODO resolve safePromise with null when monitoring has been completed - bleAdapter.monitorCharacteristic( - characteristicIdentifier, transactionId, subscriptionType, - new OnEventCallback() { - @Override - public void onEvent(Characteristic data) { - WritableArray jsResult = Arguments.createArray(); - jsResult.pushNull(); - jsResult.pushMap(characteristicConverter.toJSObject(data)); - jsResult.pushString(transactionId); - sendEvent(Event.ReadEvent, jsResult); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - safePromise.reject(getErrorCode(error), errorConverter.toJs(error)); - } - } - ); - } - - @ReactMethod - public void readDescriptorForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String descriptorUUID, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readDescriptorForDevice", promise)) { - return; - } - bleAdapter.readDescriptorForDevice( - deviceId, - serviceUUID, - characteristicUUID, - descriptorUUID, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - }); - } - - @ReactMethod - public void readDescriptorForService(final int serviceIdentifier, - final String characteristicUUID, - final String descriptorUUID, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readDescriptorForService", promise)) { - return; - } - bleAdapter.readDescriptorForService( - serviceIdentifier, - characteristicUUID, - descriptorUUID, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - }); - } - - @ReactMethod - public void readDescriptorForCharacteristic(final int characteristicIdentifier, - final String descriptorUUID, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readDescriptorForCharacteristic", promise)) { - return; - } - bleAdapter.readDescriptorForCharacteristic( - characteristicIdentifier, - descriptorUUID, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - }); - } - - @ReactMethod - public void readDescriptor(final int descriptorIdentifier, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("readDescriptor", promise)) { - return; - } - bleAdapter.readDescriptor( - descriptorIdentifier, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - }); - } - - @ReactMethod - public void writeDescriptorForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeDescriptorForDevice", promise)) { - return; - } - bleAdapter.writeDescriptorForDevice( - deviceId, - serviceUUID, - characteristicUUID, - descriptorUUID, - valueBase64, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - } - ); - } - - @ReactMethod - public void writeDescriptorForService(final int serviceIdentifier, - final String characteristicUUID, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeDescriptorForService", promise)) { - return; - } - bleAdapter.writeDescriptorForService( - serviceIdentifier, - characteristicUUID, - descriptorUUID, - valueBase64, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - } - ); - } - - @ReactMethod - public void writeDescriptorForCharacteristic(final int characteristicIdentifier, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeDescriptorForCharacteristic", promise)) { - return; - } - bleAdapter.writeDescriptorForCharacteristic( - characteristicIdentifier, - descriptorUUID, - valueBase64, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - } - ); - } - - @ReactMethod - public void writeDescriptor(final int descriptorIdentifier, - final String valueBase64, - final String transactionId, - final Promise promise) { - if (!this.isRequestPossibleHandler("writeDescriptor", promise)) { - return; - } - bleAdapter.writeDescriptor( - descriptorIdentifier, - valueBase64, - transactionId, - new OnSuccessCallback() { - @Override - public void onSuccess(Descriptor descriptor) { - promise.resolve(descriptorConverter.toJSObject(descriptor)); - } - }, - new OnErrorCallback() { - @Override - public void onError(BleError bleError) { - rejectWithBleError(promise, bleError); - } - } - ); - } - - @ReactMethod - public void addListener(String eventName) { - // Keep: Required for RN built in Event Emitter Calls. - } - - @ReactMethod - public void removeListeners(int count) { - // Keep: Required for RN built in Event Emitter Calls. - } - - private void rejectWithBleError(Promise promise, BleError error) { - String message = errorConverter.toJs(error); - promise.reject(getErrorCode(error), message == null ? ErrorDefaults.MESSAGE : message); - } - - private String getErrorCode(@Nullable BleError error) { - if (error == null || error.errorCode == null) { - return ErrorDefaults.CODE; - } - return error.errorCode.name(); - } - - private void sendEvent(@NonNull Event event, @Nullable Object params) { - getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(event.name, params); - } - - private boolean isRequestPossibleHandler(String functionName, final Promise promise) { - if(this.bleAdapter == null){ - BleError bleError = new BleError(BleErrorCode.BluetoothManagerDestroyed, String.format("BleManager cannot call the %s function because BleManager has been destroyed", functionName), null); - - rejectWithBleError(promise, bleError); - return false; - } - - return true; - } -} diff --git a/android/src/main/java/com/bleplx/BlePlxPackage.java b/android/src/main/java/com/bleplx/BlePlxPackage.java deleted file mode 100644 index 5ed13336f..000000000 --- a/android/src/main/java/com/bleplx/BlePlxPackage.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.bleplx; - -import androidx.annotation.NonNull; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class BlePlxPackage implements ReactPackage { - @NonNull - @Override - public List createNativeModules(@NonNull ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - modules.add(new BlePlxModule(reactContext)); - return modules; - } - - @NonNull - @Override - public List createViewManagers(@NonNull ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} diff --git a/android/src/main/java/com/bleplx/Event.java b/android/src/main/java/com/bleplx/Event.java deleted file mode 100644 index cadcde882..000000000 --- a/android/src/main/java/com/bleplx/Event.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.bleplx; - -public enum Event { - - ScanEvent("ScanEvent"), - ReadEvent("ReadEvent"), - StateChangeEvent("StateChangeEvent"), - RestoreStateEvent("RestoreStateEvent"), - DisconnectionEvent("DisconnectionEvent"); - - public String name; - - Event(String name) { - this.name = name; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/AdvertisementData.java b/android/src/main/java/com/bleplx/adapter/AdvertisementData.java deleted file mode 100644 index 21a6d51f2..000000000 --- a/android/src/main/java/com/bleplx/adapter/AdvertisementData.java +++ /dev/null @@ -1,251 +0,0 @@ -package com.bleplx.adapter; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -public class AdvertisementData { - - private byte[] manufacturerData; - private Map serviceData; - private List serviceUUIDs; - private String localName; - private Integer txPowerLevel; - private List solicitedServiceUUIDs; - private byte[] rawScanRecord; - - private static final long BLUETOOTH_BASE_UUID_LSB = 0x800000805F9B34FBL; - private static final int BLUETOOTH_BASE_UUID_MSB = 0x00001000; - - public String getLocalName() { - return localName; - } - - public byte[] getManufacturerData() { - return manufacturerData; - } - - public Map getServiceData() { - return serviceData; - } - - public List getServiceUUIDs() { - return serviceUUIDs; - } - - public Integer getTxPowerLevel() { - return txPowerLevel; - } - - public List getSolicitedServiceUUIDs() { - return solicitedServiceUUIDs; - } - - public byte[] getRawScanRecord() { - return rawScanRecord; - } - - private AdvertisementData() { - } - - public AdvertisementData(byte[] manufacturerData, - Map serviceData, - List serviceUUIDs, - String localName, - Integer txPowerLevel, - List solicitedServiceUUIDs) { - this.manufacturerData = manufacturerData; - this.serviceData = serviceData; - this.serviceUUIDs = serviceUUIDs; - this.localName = localName; - this.txPowerLevel = txPowerLevel; - this.solicitedServiceUUIDs = solicitedServiceUUIDs; - } - - public static AdvertisementData parseScanResponseData(byte[] advertisement) { - AdvertisementData advData = new AdvertisementData(); - advData.rawScanRecord = advertisement; - - ByteBuffer rawData = ByteBuffer.wrap(advertisement).order(ByteOrder.LITTLE_ENDIAN); - while (rawData.remaining() >= 2) { - int adLength = rawData.get() & 0xFF; - if (adLength == 0) break; - adLength -= 1; - int adType = rawData.get() & 0xFF; - if (rawData.remaining() < adLength) break; - parseAdvertisementData(advData, adType, adLength, rawData.slice().order(ByteOrder.LITTLE_ENDIAN)); - rawData.position(rawData.position() + adLength); - } - return advData; - } - - private static void parseAdvertisementData(AdvertisementData advData, int adType, int adLength, ByteBuffer data) { - switch (adType) { - case 0xFF: - parseManufacturerData(advData, adLength, data); - break; - - case 0x02: - case 0x03: - parseServiceUUIDs(advData, adLength, data, 2); - break; - case 0x04: - case 0x05: - parseServiceUUIDs(advData, adLength, data, 4); - break; - case 0x06: - case 0x07: - parseServiceUUIDs(advData, adLength, data, 16); - break; - - case 0x08: - case 0x09: - parseLocalName(advData, adType, adLength, data); - break; - - case 0x0A: - parseTxPowerLevel(advData, adLength, data); - break; - - case 0x14: - parseSolicitedServiceUUIDs(advData, adLength, data, 2); - break; - case 0x1F: - parseSolicitedServiceUUIDs(advData, adLength, data, 4); - break; - case 0x15: - parseSolicitedServiceUUIDs(advData, adLength, data, 16); - break; - - case 0x16: - parseServiceData(advData, adLength, data, 2); - break; - case 0x20: - parseServiceData(advData, adLength, data, 4); - break; - case 0x21: - parseServiceData(advData, adLength, data, 16); - break; - } - } - - private static void parseLocalName(AdvertisementData advData, int adType, int adLength, ByteBuffer data) { - // Complete local name is preferred over short local name. - if (advData.localName == null || adType == 0x09) { - byte[] bytes = new byte[adLength]; - data.get(bytes, 0, adLength); - advData.localName = new String(bytes, Charset.forName("UTF-8")); - } - } - - private static UUID parseUUID(ByteBuffer data, int uuidLength) { - long lsb; - long msb; - switch (uuidLength) { - case 2: - msb = (((long) data.getShort() & 0xFFFF) << 32) + BLUETOOTH_BASE_UUID_MSB; - lsb = BLUETOOTH_BASE_UUID_LSB; - break; - case 4: - msb = ((long) data.getInt() << 32) + BLUETOOTH_BASE_UUID_MSB; - lsb = BLUETOOTH_BASE_UUID_LSB; - break; - case 16: - lsb = data.getLong(); - msb = data.getLong(); - break; - default: - data.position(data.position() + uuidLength); - return null; - } - return new UUID(msb, lsb); - } - - private static void parseSolicitedServiceUUIDs(AdvertisementData advData, int adLength, ByteBuffer data, int uuidLength) { - if (advData.solicitedServiceUUIDs == null) advData.solicitedServiceUUIDs = new ArrayList<>(); - while (data.remaining() >= uuidLength && data.position() < adLength) { - advData.solicitedServiceUUIDs.add(parseUUID(data, uuidLength)); - } - } - - private static void parseServiceUUIDs(AdvertisementData advData, int adLength, ByteBuffer data, int uuidLength) { - if (advData.serviceUUIDs == null) advData.serviceUUIDs = new ArrayList<>(); - while (data.remaining() >= uuidLength && data.position() < adLength) { - advData.serviceUUIDs.add(parseUUID(data, uuidLength)); - } - } - - private static void parseServiceData(AdvertisementData advData, int adLength, ByteBuffer data, int uuidLength) { - if (adLength < uuidLength) return; - if (advData.serviceData == null) advData.serviceData = new HashMap<>(); - UUID serviceUUID = parseUUID(data, uuidLength); - int serviceDataLength = adLength - uuidLength; - byte[] serviceData = new byte[serviceDataLength]; - data.get(serviceData, 0, serviceDataLength); - advData.serviceData.put(serviceUUID, serviceData); - } - - private static void parseTxPowerLevel(AdvertisementData advData, int adLength, ByteBuffer data) { - if (adLength != 1) return; - advData.txPowerLevel = (int) data.get(); - } - - private static void parseManufacturerData(AdvertisementData advData, int adLength, ByteBuffer data) { - if (adLength < 2) return; - advData.manufacturerData = new byte[adLength]; - data.get(advData.manufacturerData, 0, adLength); - } - - @Override - public String toString() { - return "AdvertisementData{" + - "manufacturerData=" + Arrays.toString(manufacturerData) + - ", serviceData=" + serviceData + - ", serviceUUIDs=" + serviceUUIDs + - ", localName='" + localName + '\'' + - ", txPowerLevel=" + txPowerLevel + - ", solicitedServiceUUIDs=" + solicitedServiceUUIDs + - ", rawScanRecord=" + Arrays.toString(rawScanRecord) + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AdvertisementData that = (AdvertisementData) o; - - if (!Arrays.equals(manufacturerData, that.manufacturerData)) return false; - if (!Objects.equals(serviceData, that.serviceData)) - return false; - if (!Objects.equals(serviceUUIDs, that.serviceUUIDs)) - return false; - if (!Objects.equals(localName, that.localName)) - return false; - if (!Objects.equals(txPowerLevel, that.txPowerLevel)) - return false; - if (!Objects.equals(solicitedServiceUUIDs, that.solicitedServiceUUIDs)) - return false; - return Arrays.equals(rawScanRecord, that.rawScanRecord); - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(manufacturerData); - result = 31 * result + (serviceData != null ? serviceData.hashCode() : 0); - result = 31 * result + (serviceUUIDs != null ? serviceUUIDs.hashCode() : 0); - result = 31 * result + (localName != null ? localName.hashCode() : 0); - result = 31 * result + (txPowerLevel != null ? txPowerLevel.hashCode() : 0); - result = 31 * result + (solicitedServiceUUIDs != null ? solicitedServiceUUIDs.hashCode() : 0); - result = 31 * result + Arrays.hashCode(rawScanRecord); - return result; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/BleAdapter.java b/android/src/main/java/com/bleplx/adapter/BleAdapter.java deleted file mode 100644 index 07f0995d7..000000000 --- a/android/src/main/java/com/bleplx/adapter/BleAdapter.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.bleplx.adapter; - -import com.bleplx.adapter.errors.BleError; - -import java.util.List; - -public interface BleAdapter { - - void createClient(String restoreStateIdentifier, - OnEventCallback onAdapterStateChangeCallback, - OnEventCallback onStateRestored); - - void destroyClient(); - - void enable( - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void disable( - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - String getCurrentState(); - - void startDeviceScan( - String[] filteredUUIDs, - int scanMode, - int callbackType, - boolean legacyScan, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback); - - void stopDeviceScan(); - - void requestConnectionPriorityForDevice( - String deviceIdentifier, - int connectionPriority, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void readRSSIForDevice( - String deviceIdentifier, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void requestMTUForDevice( - String deviceIdentifier, - int mtu, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void getKnownDevices( - String[] deviceIdentifiers, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void getConnectedDevices( - String[] serviceUUIDs, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void connectToDevice( - String deviceIdentifier, - ConnectionOptions connectionOptions, - OnSuccessCallback onSuccessCallback, - OnEventCallback onConnectionStateChangedCallback, - OnErrorCallback onErrorCallback); - - void cancelDeviceConnection( - String deviceIdentifier, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void isDeviceConnected( - String deviceIdentifier, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void discoverAllServicesAndCharacteristicsForDevice( - String deviceIdentifier, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - List getServicesForDevice( - String deviceIdentifier) throws BleError; - - List getCharacteristicsForDevice( - String deviceIdentifier, - String serviceUUID) throws BleError; - - List getCharacteristicsForService( - int serviceIdentifier) throws BleError; - - List descriptorsForDevice( - String deviceIdentifier, - String serviceUUID, - String characteristicUUID) throws BleError; - - List descriptorsForService( - int serviceIdentifier, - String characteristicUUID) throws BleError; - - List descriptorsForCharacteristic( - int characteristicIdentifier) throws BleError; - - - void readCharacteristicForDevice( - String deviceIdentifier, - String serviceUUID, - String characteristicUUID, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void readCharacteristicForService( - int serviceIdentifier, - String characteristicUUID, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void readCharacteristic( - int characteristicIdentifer, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void writeCharacteristicForDevice( - String deviceIdentifier, - String serviceUUID, - String characteristicUUID, - String valueBase64, - boolean withResponse, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void writeCharacteristicForService( - int serviceIdentifier, - String characteristicUUID, - String valueBase64, - boolean withResponse, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void writeCharacteristic( - int characteristicIdentifier, - String valueBase64, - boolean withResponse, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void monitorCharacteristicForDevice( - String deviceIdentifier, - String serviceUUID, - String characteristicUUID, - String transactionId, - String subscriptionType, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback); - - void monitorCharacteristicForService( - int serviceIdentifier, - String characteristicUUID, - String transactionId, - String subscriptionType, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback); - - void monitorCharacteristic( - int characteristicIdentifier, - String transactionId, - String subscriptionType, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback); - - void readDescriptorForDevice( - final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String descriptorUUID, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void readDescriptorForService( - final int serviceIdentifier, - final String characteristicUUID, - final String descriptorUUID, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void readDescriptorForCharacteristic( - final int characteristicIdentifier, - final String descriptorUUID, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void readDescriptor( - final int descriptorIdentifier, - final String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback); - - void writeDescriptorForDevice( - final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void writeDescriptorForService( - final int serviceIdentifier, - final String characteristicUUID, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void writeDescriptorForCharacteristic( - final int characteristicIdentifier, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void writeDescriptor( - final int descriptorIdentifier, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback); - - void cancelTransaction(String transactionId); - - void setLogLevel(String logLevel); - - String getLogLevel(); -} diff --git a/android/src/main/java/com/bleplx/adapter/BleAdapterCreator.java b/android/src/main/java/com/bleplx/adapter/BleAdapterCreator.java deleted file mode 100644 index 93223f140..000000000 --- a/android/src/main/java/com/bleplx/adapter/BleAdapterCreator.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.bleplx.adapter; - -import android.content.Context; - -public interface BleAdapterCreator { - BleAdapter createAdapter(Context context); -} diff --git a/android/src/main/java/com/bleplx/adapter/BleAdapterFactory.java b/android/src/main/java/com/bleplx/adapter/BleAdapterFactory.java deleted file mode 100644 index 57fc3a633..000000000 --- a/android/src/main/java/com/bleplx/adapter/BleAdapterFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.bleplx.adapter; - -import android.content.Context; - -public class BleAdapterFactory { - - private static BleAdapterCreator bleAdapterCreator = new BleAdapterCreator() { - @Override - public BleAdapter createAdapter(Context context) { - return new BleModule(context); - } - }; - - public static BleAdapter getNewAdapter(Context context) { - return bleAdapterCreator.createAdapter(context); - } - - public static void setBleAdapterCreator(BleAdapterCreator bleAdapterCreator) { - BleAdapterFactory.bleAdapterCreator = bleAdapterCreator; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/BleModule.java b/android/src/main/java/com/bleplx/adapter/BleModule.java deleted file mode 100755 index 44392b65d..000000000 --- a/android/src/main/java/com/bleplx/adapter/BleModule.java +++ /dev/null @@ -1,1612 +0,0 @@ -package com.bleplx.adapter; - -import static com.bleplx.adapter.utils.Constants.BluetoothState; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.ParcelUuid; -import android.util.SparseArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bleplx.adapter.errors.BleError; -import com.bleplx.adapter.errors.BleErrorCode; -import com.bleplx.adapter.errors.BleErrorUtils; -import com.bleplx.adapter.errors.ErrorConverter; -import com.bleplx.adapter.exceptions.CannotMonitorCharacteristicException; -import com.bleplx.adapter.utils.Base64Converter; -import com.bleplx.adapter.utils.Constants; -import com.bleplx.adapter.utils.DisposableMap; -import com.bleplx.adapter.utils.IdGenerator; -import com.bleplx.adapter.utils.LogLevel; -import com.bleplx.adapter.utils.RefreshGattCustomOperation; -import com.bleplx.adapter.utils.SafeExecutor; -import com.bleplx.adapter.utils.ServiceFactory; -import com.bleplx.adapter.utils.UUIDConverter; -import com.bleplx.adapter.utils.mapper.RxBleDeviceToDeviceMapper; -import com.bleplx.adapter.utils.mapper.RxScanResultToScanResultMapper; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.polidea.rxandroidble2.NotificationSetupMode; -import com.polidea.rxandroidble2.RxBleAdapterStateObservable; -import com.polidea.rxandroidble2.RxBleClient; -import com.polidea.rxandroidble2.RxBleConnection; -import com.polidea.rxandroidble2.RxBleDevice; -import com.polidea.rxandroidble2.internal.RxBleLog; -import com.polidea.rxandroidble2.scan.ScanFilter; -import com.polidea.rxandroidble2.scan.ScanSettings; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import io.reactivex.BackpressureStrategy; -import io.reactivex.Observable; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.schedulers.Schedulers; - -public class BleModule extends ReactContextBaseJavaModule implements BleAdapter { - public static final String NAME = "Ble"; - - private final ErrorConverter errorConverter = new ErrorConverter(); - - @Nullable - private RxBleClient rxBleClient; - - private final HashMap discoveredDevices = new HashMap<>(); - - private final HashMap connectedDevices = new HashMap<>(); - - private final HashMap activeConnections = new HashMap<>(); - - private final SparseArray discoveredServices = new SparseArray<>(); - - private final SparseArray discoveredCharacteristics = new SparseArray<>(); - - private final SparseArray discoveredDescriptors = new SparseArray<>(); - - private final DisposableMap pendingTransactions = new DisposableMap(); - - private final DisposableMap connectingDevices = new DisposableMap(); - - private final BluetoothManager bluetoothManager; - - private final BluetoothAdapter bluetoothAdapter; - - private final Context context; - - @Nullable - private Disposable scanSubscription; - - @Nullable - private Disposable adapterStateChangesSubscription; - - private final RxBleDeviceToDeviceMapper rxBleDeviceToDeviceMapper = new RxBleDeviceToDeviceMapper(); - - private final RxScanResultToScanResultMapper rxScanResultToScanResultMapper = new RxScanResultToScanResultMapper(); - - private final ServiceFactory serviceFactory = new ServiceFactory(); - - private int currentLogLevel = RxBleLog.NONE; - - @Override - @NonNull - public String getName() { - return NAME; - } - - public BleModule(Context context) { - this.context = context; - bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); - bluetoothAdapter = bluetoothManager.getAdapter(); - } - - @Override - public void createClient(String restoreStateIdentifier, - OnEventCallback onAdapterStateChangeCallback, - OnEventCallback onStateRestored) { - rxBleClient = RxBleClient.create(context); - adapterStateChangesSubscription = monitorAdapterStateChanges(context, onAdapterStateChangeCallback); - - // We need to send signal that BLE Module starts without restored state - if (restoreStateIdentifier != null) { - onStateRestored.onEvent(null); - } - } - - private void clearActiveConnections() { - pendingTransactions.removeAllSubscriptions(); - connectingDevices.removeAllSubscriptions(); - connectedDevices.clear(); - activeConnections.clear(); - discoveredDevices.clear(); - - } - - @Override - public void destroyClient() { - if (adapterStateChangesSubscription != null) { - adapterStateChangesSubscription.dispose(); - adapterStateChangesSubscription = null; - } - if (scanSubscription != null && !scanSubscription.isDisposed()) { - scanSubscription.dispose(); - scanSubscription = null; - } - clearActiveConnections(); - discoveredServices.clear(); - discoveredCharacteristics.clear(); - discoveredDescriptors.clear(); - - rxBleClient = null; - IdGenerator.clear(); - } - - - @Override - public void enable(final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - changeAdapterState( - RxBleAdapterStateObservable.BleAdapterState.STATE_ON, - transactionId, - onSuccessCallback, - onErrorCallback); - } - - @Override - public void disable(final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - changeAdapterState( - RxBleAdapterStateObservable.BleAdapterState.STATE_OFF, - transactionId, - onSuccessCallback, - onErrorCallback); - } - - @BluetoothState - @Override - public String getCurrentState() { - if (!supportsBluetoothLowEnergy()) return BluetoothState.UNSUPPORTED; - if (bluetoothManager == null) return BluetoothState.POWERED_OFF; - return mapNativeAdapterStateToLocalBluetoothState(bluetoothAdapter.getState()); - } - - @Override - public void startDeviceScan(String[] filteredUUIDs, - int scanMode, - int callbackType, - boolean legacyScan, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback) { - UUID[] uuids = null; - - if (filteredUUIDs != null) { - uuids = UUIDConverter.convert(filteredUUIDs); - if (uuids == null) { - onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(filteredUUIDs)); - return; - } - } - - safeStartDeviceScan(uuids, scanMode, callbackType, legacyScan, onEventCallback, onErrorCallback); - } - - @Override - public void stopDeviceScan() { - if (scanSubscription != null) { - scanSubscription.dispose(); - scanSubscription = null; - } - } - - @Override - public void requestConnectionPriorityForDevice(String deviceIdentifier, - int connectionPriority, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - final Device device; - try { - device = getDeviceById(deviceIdentifier); - } catch (BleError error) { - onErrorCallback.onError(error); - return; - } - - final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .requestConnectionPriority(connectionPriority, 1, TimeUnit.MILLISECONDS) - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }).subscribe((Action) () -> { - safeExecutor.success(device); - pendingTransactions.removeSubscription(transactionId); - }, throwable -> { - safeExecutor.error(errorConverter.toError(throwable)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - @Override - public void readRSSIForDevice(String deviceIdentifier, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - final Device device; - try { - device = getDeviceById(deviceIdentifier); - } catch (BleError error) { - onErrorCallback.onError(error); - return; - } - final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .readRssi() - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(rssi -> { - device.setRssi(rssi); - safeExecutor.success(device); - pendingTransactions.removeSubscription(transactionId); - }, error -> { - safeExecutor.error(errorConverter.toError(error)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - @Override - public void requestMTUForDevice(String deviceIdentifier, int mtu, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - final Device device; - try { - device = getDeviceById(deviceIdentifier); - } catch (BleError error) { - onErrorCallback.onError(error); - return; - } - - final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .requestMtu(mtu) - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }).subscribe(outputMtu -> { - device.setMtu(outputMtu); - safeExecutor.success(device); - pendingTransactions.removeSubscription(transactionId); - }, error -> { - safeExecutor.error(errorConverter.toError(error)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - @Override - public void getKnownDevices(String[] deviceIdentifiers, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - if (rxBleClient == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to get known devices", null)); - return; - } - - List knownDevices = new ArrayList<>(); - for (final String deviceId : deviceIdentifiers) { - if (deviceId == null) { - onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(deviceIdentifiers)); - return; - } - - final Device device = discoveredDevices.get(deviceId); - if (device != null) { - knownDevices.add(device); - } - } - - onSuccessCallback.onSuccess(knownDevices.toArray(new Device[knownDevices.size()])); - } - - @Override - public void getConnectedDevices(String[] serviceUUIDs, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - if (rxBleClient == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to get connected devices", null)); - return; - } - - if (serviceUUIDs.length == 0) { - onSuccessCallback.onSuccess(new Device[0]); - return; - } - - UUID[] uuids = new UUID[serviceUUIDs.length]; - for (int i = 0; i < serviceUUIDs.length; i++) { - UUID uuid = UUIDConverter.convert(serviceUUIDs[i]); - - if (uuid == null) { - onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(serviceUUIDs)); - return; - } - - uuids[i] = uuid; - } - - List localConnectedDevices = new ArrayList<>(); - for (Device device : connectedDevices.values()) { - for (UUID uuid : uuids) { - if (device.getServiceByUUID(uuid) != null) { - localConnectedDevices.add(device); - break; - } - } - } - - onSuccessCallback.onSuccess(localConnectedDevices.toArray(new Device[localConnectedDevices.size()])); - - } - - @Override - public void connectToDevice(String deviceIdentifier, - ConnectionOptions connectionOptions, - OnSuccessCallback onSuccessCallback, - OnEventCallback onConnectionStateChangedCallback, - OnErrorCallback onErrorCallback) { - if (rxBleClient == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to connect to device", null)); - return; - } - - final RxBleDevice device = rxBleClient.getBleDevice(deviceIdentifier); - if (device == null) { - onErrorCallback.onError(BleErrorUtils.deviceNotFound(deviceIdentifier)); - return; - } - - safeConnectToDevice( - device, - connectionOptions.getAutoConnect(), - connectionOptions.getRequestMTU(), - connectionOptions.getRefreshGattMoment(), - connectionOptions.getTimeoutInMillis(), - connectionOptions.getConnectionPriority(), - onSuccessCallback, onConnectionStateChangedCallback, onErrorCallback); - } - - @Override - public void cancelDeviceConnection(String deviceIdentifier, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - if (rxBleClient == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to cancel device connection", null)); - return; - } - - final RxBleDevice device = rxBleClient.getBleDevice(deviceIdentifier); - - if (connectingDevices.removeSubscription(deviceIdentifier) && device != null) { - onSuccessCallback.onSuccess(rxBleDeviceToDeviceMapper.map(device, null)); - } else { - if (device == null) { - onErrorCallback.onError(BleErrorUtils.deviceNotFound(deviceIdentifier)); - } else { - onErrorCallback.onError(BleErrorUtils.deviceNotConnected(deviceIdentifier)); - } - } - } - - @Override - public void isDeviceConnected(String deviceIdentifier, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - if (rxBleClient == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to check if device is connected", null)); - return; - } - - try { - final RxBleDevice device = rxBleClient.getBleDevice(deviceIdentifier); - if (device == null) { - onErrorCallback.onError(BleErrorUtils.deviceNotFound(deviceIdentifier)); - return; - } - - boolean connected = device.getConnectionState() - .equals(RxBleConnection.RxBleConnectionState.CONNECTED); - onSuccessCallback.onSuccess(connected); - } catch (Exception e) { - RxBleLog.e(e, "Error while checking if device is connected"); - onErrorCallback.onError(errorConverter.toError(e)); - } - } - - @Override - public void discoverAllServicesAndCharacteristicsForDevice(String deviceIdentifier, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Device device; - try { - device = getDeviceById(deviceIdentifier); - } catch (BleError error) { - onErrorCallback.onError(error); - return; - } - - safeDiscoverAllServicesAndCharacteristicsForDevice(device, transactionId, onSuccessCallback, onErrorCallback); - } - - @Override - public List getServicesForDevice(String deviceIdentifier) throws BleError { - final Device device = getDeviceById(deviceIdentifier); - final List services = device.getServices(); - if (services == null) { - throw BleErrorUtils.deviceServicesNotDiscovered(device.getId()); - } - return services; - } - - @Override - public List getCharacteristicsForDevice(String deviceIdentifier, - String serviceUUID) throws BleError { - final UUID convertedServiceUUID = UUIDConverter.convert(serviceUUID); - if (convertedServiceUUID == null) { - throw BleErrorUtils.invalidIdentifiers(serviceUUID); - } - - final Device device = getDeviceById(deviceIdentifier); - - final Service service = device.getServiceByUUID(convertedServiceUUID); - if (service == null) { - throw BleErrorUtils.serviceNotFound(serviceUUID); - } - - return service.getCharacteristics(); - } - - @Override - public List getCharacteristicsForService(int serviceIdentifier) throws BleError { - Service service = discoveredServices.get(serviceIdentifier); - if (service == null) { - throw BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier)); - } - return service.getCharacteristics(); - } - - @Override - public List descriptorsForDevice(final String deviceIdentifier, - final String serviceUUID, - final String characteristicUUID) throws BleError { - final UUID[] uuids = UUIDConverter.convert(serviceUUID, characteristicUUID); - if (uuids == null) { - throw BleErrorUtils.invalidIdentifiers(serviceUUID, characteristicUUID); - } - - Device device = getDeviceById(deviceIdentifier); - - final Service service = device.getServiceByUUID(uuids[0]); - if (service == null) { - throw BleErrorUtils.serviceNotFound(serviceUUID); - } - - final Characteristic characteristic = service.getCharacteristicByUUID(uuids[1]); - if (characteristic == null) { - throw BleErrorUtils.characteristicNotFound(characteristicUUID); - } - - return characteristic.getDescriptors(); - } - - @Override - public List descriptorsForService(final int serviceIdentifier, - final String characteristicUUID) throws BleError { - final UUID uuid = UUIDConverter.convert(characteristicUUID); - if (uuid == null) { - throw BleErrorUtils.invalidIdentifiers(characteristicUUID); - } - - Service service = discoveredServices.get(serviceIdentifier); - if (service == null) { - throw BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier)); - } - - final Characteristic characteristic = service.getCharacteristicByUUID(uuid); - if (characteristic == null) { - throw BleErrorUtils.characteristicNotFound(characteristicUUID); - } - - return characteristic.getDescriptors(); - } - - @Override - public List descriptorsForCharacteristic(final int characteristicIdentifier) throws BleError { - Characteristic characteristic = discoveredCharacteristics.get(characteristicIdentifier); - if (characteristic == null) { - throw BleErrorUtils.characteristicNotFound(Integer.toString(characteristicIdentifier)); - } - - return characteristic.getDescriptors(); - } - - @Override - public void readCharacteristicForDevice(String deviceIdentifier, - String serviceUUID, - String characteristicUUID, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError( - deviceIdentifier, serviceUUID, characteristicUUID, onErrorCallback); - if (characteristic == null) { - return; - } - - safeReadCharacteristicForDevice(characteristic, transactionId, onSuccessCallback, onErrorCallback); - } - - @Override - public void readCharacteristicForService(int serviceIdentifier, - String characteristicUUID, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError( - serviceIdentifier, characteristicUUID, onErrorCallback); - if (characteristic == null) { - return; - } - - safeReadCharacteristicForDevice(characteristic, transactionId, onSuccessCallback, onErrorCallback); - } - - @Override - public void readCharacteristic(int characteristicIdentifier, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError(characteristicIdentifier, onErrorCallback); - if (characteristic == null) { - return; - } - - safeReadCharacteristicForDevice(characteristic, transactionId, onSuccessCallback, onErrorCallback); - } - - @Override - public void writeCharacteristicForDevice(String deviceIdentifier, - String serviceUUID, - String characteristicUUID, - String valueBase64, - boolean withResponse, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError( - deviceIdentifier, serviceUUID, characteristicUUID, onErrorCallback); - if (characteristic == null) { - return; - } - - writeCharacteristicWithValue( - characteristic, - valueBase64, - withResponse, - transactionId, - onSuccessCallback, - onErrorCallback); - } - - @Override - public void writeCharacteristicForService(int serviceIdentifier, - String characteristicUUID, - String valueBase64, - boolean withResponse, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError( - serviceIdentifier, characteristicUUID, onErrorCallback); - if (characteristic == null) { - return; - } - - writeCharacteristicWithValue( - characteristic, - valueBase64, - withResponse, - transactionId, - onSuccessCallback, - onErrorCallback); - } - - @Override - public void writeCharacteristic(int characteristicIdentifier, - String valueBase64, - boolean withResponse, - String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError(characteristicIdentifier, onErrorCallback); - if (characteristic == null) { - return; - } - - writeCharacteristicWithValue( - characteristic, - valueBase64, - withResponse, - transactionId, - onSuccessCallback, - onErrorCallback - ); - } - - @Override - public void monitorCharacteristicForDevice(String deviceIdentifier, - String serviceUUID, - String characteristicUUID, - String transactionId, - String subscriptionType, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError( - deviceIdentifier, serviceUUID, characteristicUUID, onErrorCallback); - if (characteristic == null) { - return; - } - - safeMonitorCharacteristicForDevice(characteristic, transactionId, subscriptionType, onEventCallback, onErrorCallback); - } - - @Override - public void monitorCharacteristicForService(int serviceIdentifier, - String characteristicUUID, - String transactionId, - String subscriptionType, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError( - serviceIdentifier, characteristicUUID, onErrorCallback); - if (characteristic == null) { - return; - } - - safeMonitorCharacteristicForDevice(characteristic, transactionId, subscriptionType, onEventCallback, onErrorCallback); - } - - @Override - public void monitorCharacteristic(int characteristicIdentifier, String transactionId, String subscriptionType, - OnEventCallback onEventCallback, - OnErrorCallback onErrorCallback) { - final Characteristic characteristic = getCharacteristicOrEmitError(characteristicIdentifier, onErrorCallback); - if (characteristic == null) { - return; - } - - safeMonitorCharacteristicForDevice(characteristic, transactionId, subscriptionType, onEventCallback, onErrorCallback); - } - - @Override - public void readDescriptorForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String descriptorUUID, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - - try { - Descriptor descriptor = getDescriptor(deviceId, serviceUUID, characteristicUUID, descriptorUUID); - safeReadDescriptorForDevice(descriptor, transactionId, successCallback, errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - } - - @Override - public void readDescriptorForService(final int serviceIdentifier, - final String characteristicUUID, - final String descriptorUUID, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - try { - Descriptor descriptor = getDescriptor(serviceIdentifier, characteristicUUID, descriptorUUID); - safeReadDescriptorForDevice(descriptor, transactionId, successCallback, errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - } - - @Override - public void readDescriptorForCharacteristic(final int characteristicIdentifier, - final String descriptorUUID, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - - try { - Descriptor descriptor = getDescriptor(characteristicIdentifier, descriptorUUID); - safeReadDescriptorForDevice(descriptor, transactionId, successCallback, errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - } - - @Override - public void readDescriptor(final int descriptorIdentifier, - final String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - try { - Descriptor descriptor = getDescriptor(descriptorIdentifier); - safeReadDescriptorForDevice(descriptor, transactionId, onSuccessCallback, onErrorCallback); - } catch (BleError error) { - onErrorCallback.onError(error); - } - } - - private void safeReadDescriptorForDevice(final Descriptor descriptor, - final String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final RxBleConnection connection = getConnectionOrEmitError(descriptor.getDeviceId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .readDescriptor(descriptor.getNativeDescriptor()) - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(bytes -> { - descriptor.logValue("Read from", bytes); - descriptor.setValue(bytes); - safeExecutor.success(new Descriptor(descriptor)); - pendingTransactions.removeSubscription(transactionId); - }, error -> { - safeExecutor.error(errorConverter.toError(error)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - @Override - public void writeDescriptorForDevice(final String deviceId, - final String serviceUUID, - final String characteristicUUID, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - try { - Descriptor descriptor = getDescriptor(deviceId, serviceUUID, characteristicUUID, descriptorUUID); - safeWriteDescriptorForDevice( - descriptor, - valueBase64, - transactionId, - successCallback, - errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - } - - @Override - public void writeDescriptorForService(final int serviceIdentifier, - final String characteristicUUID, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - try { - Descriptor descriptor = getDescriptor(serviceIdentifier, characteristicUUID, descriptorUUID); - safeWriteDescriptorForDevice( - descriptor, - valueBase64, - transactionId, - successCallback, - errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - } - - @Override - public void writeDescriptorForCharacteristic(final int characteristicIdentifier, - final String descriptorUUID, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - try { - Descriptor descriptor = getDescriptor(characteristicIdentifier, descriptorUUID); - safeWriteDescriptorForDevice( - descriptor, - valueBase64, - transactionId, - successCallback, - errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - } - - @Override - public void writeDescriptor(final int descriptorIdentifier, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - try { - Descriptor descriptor = getDescriptor(descriptorIdentifier); - safeWriteDescriptorForDevice( - descriptor, - valueBase64, - transactionId, - successCallback, - errorCallback); - } catch (BleError error) { - errorCallback.onError(error); - } - - } - - private void safeWriteDescriptorForDevice(final Descriptor descriptor, - final String valueBase64, - final String transactionId, - OnSuccessCallback successCallback, - OnErrorCallback errorCallback) { - BluetoothGattDescriptor nativeDescriptor = descriptor.getNativeDescriptor(); - - if (nativeDescriptor.getUuid().equals(Constants.CLIENT_CHARACTERISTIC_CONFIG_UUID)) { - errorCallback.onError(BleErrorUtils.descriptorWriteNotAllowed(UUIDConverter.fromUUID(nativeDescriptor.getUuid()))); - return; - } - - final RxBleConnection connection = getConnectionOrEmitError(descriptor.getDeviceId(), errorCallback); - if (connection == null) { - return; - } - - final byte[] value; - try { - value = Base64Converter.decode(valueBase64); - } catch (Throwable e) { - String uuid = UUIDConverter.fromUUID(nativeDescriptor.getUuid()); - errorCallback.onError(BleErrorUtils.invalidWriteDataForDescriptor(valueBase64, uuid)); - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(successCallback, errorCallback); - - final Disposable subscription = connection - .writeDescriptor(nativeDescriptor, value) - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(() -> { - descriptor.logValue("Write to", value); - descriptor.setValue(value); - safeExecutor.success(new Descriptor(descriptor)); - pendingTransactions.removeSubscription(transactionId); - }, error -> { - safeExecutor.error(errorConverter.toError(error)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - // Mark: Descriptors getters ------------------------------------------------------------------- - - private Descriptor getDescriptor(@NonNull final String deviceId, - @NonNull final String serviceUUID, - @NonNull final String characteristicUUID, - @NonNull final String descriptorUUID) throws BleError { - final UUID[] UUIDs = UUIDConverter.convert(serviceUUID, characteristicUUID, descriptorUUID); - if (UUIDs == null) { - throw BleErrorUtils.invalidIdentifiers(serviceUUID, characteristicUUID, descriptorUUID); - } - - final Device device = connectedDevices.get(deviceId); - if (device == null) { - throw BleErrorUtils.deviceNotConnected(deviceId); - } - - final Service service = device.getServiceByUUID(UUIDs[0]); - if (service == null) { - throw BleErrorUtils.serviceNotFound(serviceUUID); - } - - final Characteristic characteristic = service.getCharacteristicByUUID(UUIDs[1]); - if (characteristic == null) { - throw BleErrorUtils.characteristicNotFound(characteristicUUID); - } - - final Descriptor descriptor = characteristic.getDescriptorByUUID(UUIDs[2]); - if (descriptor == null) { - throw BleErrorUtils.descriptorNotFound(descriptorUUID); - } - - return descriptor; - } - - private Descriptor getDescriptor(final int serviceIdentifier, - @NonNull final String characteristicUUID, - @NonNull final String descriptorUUID) throws BleError { - final UUID[] UUIDs = UUIDConverter.convert(characteristicUUID, descriptorUUID); - if (UUIDs == null) { - throw BleErrorUtils.invalidIdentifiers(characteristicUUID, descriptorUUID); - } - - final Service service = discoveredServices.get(serviceIdentifier); - if (service == null) { - throw BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier)); - } - - final Characteristic characteristic = service.getCharacteristicByUUID(UUIDs[0]); - if (characteristic == null) { - throw BleErrorUtils.characteristicNotFound(characteristicUUID); - } - - final Descriptor descriptor = characteristic.getDescriptorByUUID(UUIDs[1]); - if (descriptor == null) { - throw BleErrorUtils.descriptorNotFound(descriptorUUID); - } - - return descriptor; - } - - private Descriptor getDescriptor(final int characteristicIdentifier, - @NonNull final String descriptorUUID) throws BleError { - final UUID uuid = UUIDConverter.convert(descriptorUUID); - if (uuid == null) { - throw BleErrorUtils.invalidIdentifiers(descriptorUUID); - } - - final Characteristic characteristic = discoveredCharacteristics.get(characteristicIdentifier); - if (characteristic == null) { - throw BleErrorUtils.characteristicNotFound(Integer.toString(characteristicIdentifier)); - } - - final Descriptor descriptor = characteristic.getDescriptorByUUID(uuid); - if (descriptor == null) { - throw BleErrorUtils.descriptorNotFound(descriptorUUID); - } - - return descriptor; - } - - private Descriptor getDescriptor(final int descriptorIdentifier) throws BleError { - - final Descriptor descriptor = discoveredDescriptors.get(descriptorIdentifier); - if (descriptor == null) { - throw BleErrorUtils.descriptorNotFound(Integer.toString(descriptorIdentifier)); - } - - return descriptor; - } - - @Override - public void cancelTransaction(String transactionId) { - pendingTransactions.removeSubscription(transactionId); - } - - public void setLogLevel(String logLevel) { - currentLogLevel = LogLevel.toLogLevel(logLevel); - RxBleLog.setLogLevel(currentLogLevel); - } - - @Override - public String getLogLevel() { - return LogLevel.fromLogLevel(currentLogLevel); - } - - private Disposable monitorAdapterStateChanges(Context context, - final OnEventCallback onAdapterStateChangeCallback) { - if (!supportsBluetoothLowEnergy()) { - return null; - } - - return new RxBleAdapterStateObservable(context) - .map(this::mapRxBleAdapterStateToLocalBluetoothState) - .subscribe(onAdapterStateChangeCallback::onEvent); - } - - private boolean supportsBluetoothLowEnergy() { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); - } - - @BluetoothState - private String mapRxBleAdapterStateToLocalBluetoothState( - RxBleAdapterStateObservable.BleAdapterState rxBleAdapterState - ) { - if (rxBleAdapterState == RxBleAdapterStateObservable.BleAdapterState.STATE_ON) { - return BluetoothState.POWERED_ON; - } else if (rxBleAdapterState == RxBleAdapterStateObservable.BleAdapterState.STATE_OFF) { - return BluetoothState.POWERED_OFF; - } else { - return BluetoothState.RESETTING; - } - } - - @SuppressLint("MissingPermission") - private void changeAdapterState(final RxBleAdapterStateObservable.BleAdapterState desiredAdapterState, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - if (bluetoothManager == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothStateChangeFailed, "BluetoothManager is null", null)); - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = new RxBleAdapterStateObservable(context) - .takeUntil(actualAdapterState -> desiredAdapterState == actualAdapterState) - .firstOrError() - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(state -> { - safeExecutor.success(null); - pendingTransactions.removeSubscription(transactionId); - }, error -> { - safeExecutor.error(errorConverter.toError(error)); - pendingTransactions.removeSubscription(transactionId); - }); - - - boolean desiredAndInitialStateAreSame = false; - try { - if (desiredAdapterState == RxBleAdapterStateObservable.BleAdapterState.STATE_ON) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (context instanceof Activity) { - ((Activity) context).startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1); - desiredAndInitialStateAreSame = true; - } - } else { - desiredAndInitialStateAreSame = !bluetoothAdapter.enable(); - } - } else { - desiredAndInitialStateAreSame = !bluetoothAdapter.disable(); - } - } catch (SecurityException e) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - onErrorCallback.onError(new BleError( - BleErrorCode.BluetoothUnauthorized, - "Method requires BLUETOOTH_CONNECT permission", - null) - ); - } else { - onErrorCallback.onError(new BleError( - BleErrorCode.BluetoothUnauthorized, - "Method requires BLUETOOTH_ADMIN permission", - null) - ); - } - } catch (Exception e) { - onErrorCallback.onError(new BleError( - BleErrorCode.BluetoothStateChangeFailed, - String.format("Couldn't set bluetooth adapter state because of: %s", e.getMessage() != null ? e.getMessage() : "unknown error"), - null) - ); - } - if (desiredAndInitialStateAreSame) { - subscription.dispose(); - onErrorCallback.onError(new BleError( - BleErrorCode.BluetoothStateChangeFailed, - String.format("Couldn't set bluetooth adapter state to %s", desiredAdapterState.toString()), - null)); - } else { - pendingTransactions.replaceSubscription(transactionId, subscription); - } - } - - @BluetoothState - private String mapNativeAdapterStateToLocalBluetoothState(int adapterState) { - switch (adapterState) { - case BluetoothAdapter.STATE_OFF: - return BluetoothState.POWERED_OFF; - case BluetoothAdapter.STATE_ON: - return BluetoothState.POWERED_ON; - case BluetoothAdapter.STATE_TURNING_OFF: - // fallthrough - case BluetoothAdapter.STATE_TURNING_ON: - return BluetoothState.RESETTING; - default: - return BluetoothState.UNKNOWN; - } - } - - private void safeStartDeviceScan(final UUID[] uuids, - final int scanMode, - final int callbackType, - final boolean legacyScan, - final OnEventCallback onEventCallback, - final OnErrorCallback onErrorCallback) { - if (rxBleClient == null) { - onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to start device scan", null)); - return; - } - - ScanSettings scanSettings = new ScanSettings.Builder() - .setScanMode(scanMode) - .setCallbackType(callbackType) - .setLegacy(legacyScan) - .build(); - - int length = uuids == null ? 0 : uuids.length; - ScanFilter[] filters = new ScanFilter[length]; - for (int i = 0; i < length; i++) { - filters[i] = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuids[i].toString())).build(); - } - - scanSubscription = rxBleClient - .scanBleDevices(scanSettings, filters) - .subscribe(scanResult -> { - String deviceId = scanResult.getBleDevice().getMacAddress(); - if (!discoveredDevices.containsKey(deviceId)) { - discoveredDevices.put(deviceId, rxBleDeviceToDeviceMapper.map(scanResult.getBleDevice(), null)); - } - onEventCallback.onEvent(rxScanResultToScanResultMapper.map(scanResult)); - }, throwable -> onErrorCallback.onError(errorConverter.toError(throwable))); - } - - @NonNull - private Device getDeviceById(@NonNull final String deviceId) throws BleError { - final Device device = connectedDevices.get(deviceId); - if (device == null) { - throw BleErrorUtils.deviceNotConnected(deviceId); - } - return device; - } - - @Nullable - private RxBleConnection getConnectionOrEmitError(@NonNull final String deviceId, - @NonNull OnErrorCallback onErrorCallback) { - final RxBleConnection connection = activeConnections.get(deviceId); - if (connection == null) { - onErrorCallback.onError(BleErrorUtils.deviceNotConnected(deviceId)); - return null; - } - return connection; - } - - private void safeConnectToDevice(final RxBleDevice device, - final boolean autoConnect, - final int requestMtu, - final RefreshGattMoment refreshGattMoment, - final Long timeout, - final int connectionPriority, - final OnSuccessCallback onSuccessCallback, - final OnEventCallback onConnectionStateChangedCallback, - final OnErrorCallback onErrorCallback) { - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - Observable connect = device - .establishConnection(autoConnect) - .doOnSubscribe(disposable -> onConnectionStateChangedCallback.onEvent(ConnectionState.CONNECTING)) - .doFinally(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - onDeviceDisconnected(device); - onConnectionStateChangedCallback.onEvent(ConnectionState.DISCONNECTED); - }); - - if (refreshGattMoment == RefreshGattMoment.ON_CONNECTED) { - connect = connect.flatMap(rxBleConnection -> rxBleConnection - .queue(new RefreshGattCustomOperation()) - .map(refreshGattSuccess -> rxBleConnection)); - } - - if (connectionPriority > 0) { - connect = connect.flatMap(rxBleConnection -> rxBleConnection - .requestConnectionPriority(connectionPriority, 1, TimeUnit.MILLISECONDS) - .andThen(Observable.just(rxBleConnection)) - ); - } - - if (requestMtu > 0) { - connect = connect.flatMap(rxBleConnection -> - rxBleConnection.requestMtu(requestMtu) - .map(integer -> rxBleConnection) - .toObservable() - ); - } - - if (timeout != null) { - connect = connect.timeout( - Observable.timer(timeout, TimeUnit.MILLISECONDS), - item -> Observable.never() - ); - } - - - final Disposable subscription = connect - .subscribe(rxBleConnection -> { - Device localDevice = rxBleDeviceToDeviceMapper.map(device, rxBleConnection); - onConnectionStateChangedCallback.onEvent(ConnectionState.CONNECTED); - cleanServicesAndCharacteristicsForDevice(localDevice); - connectedDevices.put(device.getMacAddress(), localDevice); - activeConnections.put(device.getMacAddress(), rxBleConnection); - safeExecutor.success(localDevice); - }, error -> { - BleError bleError = errorConverter.toError(error); - safeExecutor.error(bleError); - onDeviceDisconnected(device); - }); - - connectingDevices.replaceSubscription(device.getMacAddress(), subscription); - } - - private void onDeviceDisconnected(RxBleDevice rxDevice) { - activeConnections.remove(rxDevice.getMacAddress()); - Device device = connectedDevices.remove(rxDevice.getMacAddress()); - if (device == null) { - return; - } - - cleanServicesAndCharacteristicsForDevice(device); - connectingDevices.removeSubscription(device.getId()); - } - - private void safeDiscoverAllServicesAndCharacteristicsForDevice(final Device device, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .discoverServices() - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(rxBleDeviceServices -> { - ArrayList services = new ArrayList<>(); - for (BluetoothGattService gattService : rxBleDeviceServices.getBluetoothGattServices()) { - Service service = serviceFactory.create(device.getId(), gattService); - discoveredServices.put(service.getId(), service); - services.add(service); - - for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) { - Characteristic characteristic = new Characteristic(service, gattCharacteristic); - discoveredCharacteristics.put(characteristic.getId(), characteristic); - - for (BluetoothGattDescriptor gattDescriptor : gattCharacteristic.getDescriptors()) { - Descriptor descriptor = new Descriptor(characteristic, gattDescriptor); - discoveredDescriptors.put(descriptor.getId(), descriptor); - } - } - } - device.setServices(services); - // Moved from onSuccess method from old RxJava1 implementation - safeExecutor.success(device); - pendingTransactions.removeSubscription(transactionId); - }, throwable -> { - safeExecutor.error(errorConverter.toError(throwable)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - private void safeReadCharacteristicForDevice(final Characteristic characteristic, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - final RxBleConnection connection = getConnectionOrEmitError(characteristic.getDeviceId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .readCharacteristic(characteristic.gattCharacteristic) - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(bytes -> { - characteristic.logValue("Read from", bytes); - characteristic.setValue(bytes); - safeExecutor.success(new Characteristic(characteristic)); - pendingTransactions.removeSubscription(transactionId); - }, throwable -> { - safeExecutor.error(errorConverter.toError(throwable)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - private void writeCharacteristicWithValue(final Characteristic characteristic, - final String valueBase64, - final Boolean response, - final String transactionId, - OnSuccessCallback onSuccessCallback, - OnErrorCallback onErrorCallback) { - final byte[] value; - try { - value = Base64Converter.decode(valueBase64); - } catch (Throwable error) { - onErrorCallback.onError( - BleErrorUtils.invalidWriteDataForCharacteristic(valueBase64, - UUIDConverter.fromUUID(characteristic.getUuid()))); - return; - } - - characteristic.setWriteType(response ? - BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT : - BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); - - safeWriteCharacteristicForDevice( - characteristic, - value, - transactionId, - onSuccessCallback, - onErrorCallback); - } - - private void safeWriteCharacteristicForDevice(final Characteristic characteristic, - final byte[] value, - final String transactionId, - final OnSuccessCallback onSuccessCallback, - final OnErrorCallback onErrorCallback) { - final RxBleConnection connection = getConnectionOrEmitError(characteristic.getDeviceId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback); - - final Disposable subscription = connection - .writeCharacteristic(characteristic.gattCharacteristic, value) - .doOnDispose(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .subscribe(bytes -> { - characteristic.logValue("Write to", bytes); - characteristic.setValue(bytes); - safeExecutor.success(new Characteristic(characteristic)); - pendingTransactions.removeSubscription(transactionId); - }, throwable -> { - safeExecutor.error(errorConverter.toError(throwable)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - private void safeMonitorCharacteristicForDevice(final Characteristic characteristic, - final String transactionId, - final String subscriptionType, - final OnEventCallback onEventCallback, - final OnErrorCallback onErrorCallback) { - final RxBleConnection connection = getConnectionOrEmitError(characteristic.getDeviceId(), onErrorCallback); - if (connection == null) { - return; - } - - final SafeExecutor safeExecutor = new SafeExecutor<>(null, onErrorCallback); - - final Disposable subscription = Observable.defer(() -> { - BluetoothGattDescriptor cccDescriptor = characteristic.getGattDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG_UUID); - NotificationSetupMode setupMode = cccDescriptor != null - ? NotificationSetupMode.QUICK_SETUP - : NotificationSetupMode.COMPAT; - - if ("notification".equals(subscriptionType) && characteristic.isNotifiable()) { - return connection.setupNotification(characteristic.gattCharacteristic, setupMode); - } else if ("indication".equals(subscriptionType) && characteristic.isIndicatable()) { - return connection.setupIndication(characteristic.gattCharacteristic, setupMode); - } else if (characteristic.isNotifiable()) { - return connection.setupNotification(characteristic.gattCharacteristic, setupMode); - } else if (characteristic.isIndicatable()) { - return connection.setupIndication(characteristic.gattCharacteristic, setupMode); - } - - return Observable.error(new CannotMonitorCharacteristicException(characteristic)); - }) - .flatMap(observable -> observable) - .toFlowable(BackpressureStrategy.BUFFER) - .observeOn(Schedulers.computation()) - .doOnCancel(() -> { - safeExecutor.error(BleErrorUtils.cancelled()); - pendingTransactions.removeSubscription(transactionId); - }) - .doOnComplete(() -> pendingTransactions.removeSubscription(transactionId)) - .subscribe(bytes -> { - characteristic.logValue("Notification from", bytes); - characteristic.setValue(bytes); - onEventCallback.onEvent(new Characteristic(characteristic)); - }, throwable -> { - safeExecutor.error(errorConverter.toError(throwable)); - pendingTransactions.removeSubscription(transactionId); - }); - - pendingTransactions.replaceSubscription(transactionId, subscription); - } - - @Nullable - private Characteristic getCharacteristicOrEmitError(@NonNull final String deviceId, - @NonNull final String serviceUUID, - @NonNull final String characteristicUUID, - @NonNull final OnErrorCallback onErrorCallback) { - - final UUID[] UUIDs = UUIDConverter.convert(serviceUUID, characteristicUUID); - if (UUIDs == null) { - onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(serviceUUID, characteristicUUID)); - return null; - } - - final Device device = connectedDevices.get(deviceId); - if (device == null) { - onErrorCallback.onError(BleErrorUtils.deviceNotConnected(deviceId)); - return null; - } - - final Service service = device.getServiceByUUID(UUIDs[0]); - if (service == null) { - onErrorCallback.onError(BleErrorUtils.serviceNotFound(serviceUUID)); - return null; - } - - final Characteristic characteristic = service.getCharacteristicByUUID(UUIDs[1]); - if (characteristic == null) { - onErrorCallback.onError(BleErrorUtils.characteristicNotFound(characteristicUUID)); - return null; - } - - return characteristic; - } - - @Nullable - private Characteristic getCharacteristicOrEmitError(final int serviceIdentifier, - @NonNull final String characteristicUUID, - @NonNull final OnErrorCallback onErrorCallback) { - - final UUID uuid = UUIDConverter.convert(characteristicUUID); - if (uuid == null) { - onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(characteristicUUID)); - return null; - } - - final Service service = discoveredServices.get(serviceIdentifier); - if (service == null) { - onErrorCallback.onError(BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier))); - return null; - } - - final Characteristic characteristic = service.getCharacteristicByUUID(uuid); - if (characteristic == null) { - onErrorCallback.onError(BleErrorUtils.characteristicNotFound(characteristicUUID)); - return null; - } - - return characteristic; - } - - @Nullable - private Characteristic getCharacteristicOrEmitError(final int characteristicIdentifier, - @NonNull final OnErrorCallback onErrorCallback) { - - final Characteristic characteristic = discoveredCharacteristics.get(characteristicIdentifier); - if (characteristic == null) { - onErrorCallback.onError(BleErrorUtils.characteristicNotFound(Integer.toString(characteristicIdentifier))); - return null; - } - - return characteristic; - } - - private void cleanServicesAndCharacteristicsForDevice(@NonNull Device device) { - List discoveredServicesKeysToRemove = new ArrayList<>(); - for (int i = 0; i < discoveredServices.size(); i++) { - int key = discoveredServices.keyAt(i); - Service service = discoveredServices.get(key); - if (service == null || service.getDeviceID().equals(device.getId())) { - discoveredServicesKeysToRemove.add(key); - } - } - for (int key : discoveredServicesKeysToRemove) { - if (discoveredServices.indexOfKey(key) >= 0) { - discoveredServices.remove(key); - } - } - - List discoveredCharacteristicsKeysToRemove = new ArrayList<>(); - for (int i = 0; i < discoveredCharacteristics.size(); i++) { - int key = discoveredCharacteristics.keyAt(i); - Characteristic characteristic = discoveredCharacteristics.get(key); - if (characteristic == null || characteristic.getDeviceId().equals(device.getId())) { - discoveredCharacteristicsKeysToRemove.add(key); - } - } - for (int key : discoveredCharacteristicsKeysToRemove) { - if (discoveredCharacteristics.indexOfKey(key) >= 0) { - discoveredCharacteristics.remove(key); - } - } - - List discoveredDescriptorsKeysToRemove = new ArrayList<>(); - for (int i = 0; i < discoveredDescriptors.size(); i++) { - int key = discoveredDescriptors.keyAt(i); - Descriptor descriptor = discoveredDescriptors.get(key); - if (descriptor == null || descriptor.getDeviceId().equals(device.getId())) { - discoveredDescriptorsKeysToRemove.add(key); - } - } - for (int key : discoveredDescriptorsKeysToRemove) { - if (discoveredDescriptors.indexOfKey(key) >= 0) { - discoveredDescriptors.remove(key); - } - } - } - - @Override - public void invalidate() { - clearActiveConnections(); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/Characteristic.java b/android/src/main/java/com/bleplx/adapter/Characteristic.java deleted file mode 100755 index 7208813a2..000000000 --- a/android/src/main/java/com/bleplx/adapter/Characteristic.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.bleplx.adapter; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bleplx.adapter.utils.ByteUtils; -import com.bleplx.adapter.utils.Constants; -import com.bleplx.adapter.utils.IdGenerator; -import com.bleplx.adapter.utils.IdGeneratorKey; -import com.polidea.rxandroidble2.internal.RxBleLog; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * @noinspection ALL - */ -public class Characteristic { - - final private int id; - final private int serviceID; - final private UUID serviceUUID; - final private String deviceID; - private byte[] value; - final BluetoothGattCharacteristic gattCharacteristic; - - public void setValue(byte[] value) { - this.value = value; - } - - public Characteristic(@NonNull Service service, @NonNull BluetoothGattCharacteristic gattCharacteristic) { - this.deviceID = service.getDeviceID(); - this.serviceUUID = service.getUuid(); - this.serviceID = service.getId(); - this.gattCharacteristic = gattCharacteristic; - this.id = IdGenerator.getIdForKey(new IdGeneratorKey(deviceID, gattCharacteristic.getUuid(), gattCharacteristic.getInstanceId())); - } - - public Characteristic(int id, @NonNull Service service, BluetoothGattCharacteristic gattCharacteristic) { - this.id = id; - this.deviceID = service.getDeviceID(); - this.serviceUUID = service.getUuid(); - this.serviceID = service.getId(); - this.gattCharacteristic = gattCharacteristic; - } - - public Characteristic(Characteristic other) { - id = other.id; - serviceID = other.serviceID; - serviceUUID = other.serviceUUID; - deviceID = other.deviceID; - if (other.value != null) value = other.value.clone(); - gattCharacteristic = other.gattCharacteristic; - } - - public int getId() { - return this.id; - } - - public UUID getUuid() { - return gattCharacteristic.getUuid(); - } - - public int getServiceID() { - return serviceID; - } - - public UUID getServiceUUID() { - return serviceUUID; - } - - public String getDeviceId() { - return deviceID; - } - - public int getInstanceId() { - return gattCharacteristic.getInstanceId(); - } - - public BluetoothGattDescriptor getGattDescriptor(UUID uuid) { - return gattCharacteristic.getDescriptor(uuid); - } - - public void setWriteType(int writeType) { - gattCharacteristic.setWriteType(writeType); - } - - public boolean isReadable() { - return (gattCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0; - } - - public boolean isWritableWithResponse() { - return (gattCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0; - } - - public boolean isWritableWithoutResponse() { - return (gattCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0; - } - - public boolean isNotifiable() { - return (gattCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0; - } - - public List getDescriptors() { - ArrayList descriptors = new ArrayList<>(gattCharacteristic.getDescriptors().size()); - for (BluetoothGattDescriptor gattDescriptor : gattCharacteristic.getDescriptors()) { - descriptors.add(new Descriptor(this, gattDescriptor)); - } - return descriptors; - } - - public boolean isNotifying() { - BluetoothGattDescriptor descriptor = gattCharacteristic.getDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG_UUID); - boolean isNotifying = false; - if (descriptor != null) { - byte[] descriptorValue = descriptor.getValue(); - if (descriptorValue != null) { - isNotifying = (descriptorValue[0] & 0x01) != 0; - } - } - return isNotifying; - } - - public boolean isIndicatable() { - return (gattCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0; - } - - public byte[] getValue() { - return value; - } - - @Nullable - public Descriptor getDescriptorByUUID(@NonNull UUID uuid) { - BluetoothGattDescriptor descriptor = this.gattCharacteristic.getDescriptor(uuid); - if (descriptor == null) return null; - return new Descriptor(this, descriptor); - } - - void logValue(String message, byte[] value) { - if (value == null) { - value = gattCharacteristic.getValue(); - } - String hexValue = value != null ? ByteUtils.bytesToHex(value) : "(null)"; - RxBleLog.v(message + - " Characteristic(uuid: " + gattCharacteristic.getUuid().toString() + - ", id: " + id + - ", value: " + hexValue + ")"); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/ConnectionOptions.java b/android/src/main/java/com/bleplx/adapter/ConnectionOptions.java deleted file mode 100644 index 51b0044c0..000000000 --- a/android/src/main/java/com/bleplx/adapter/ConnectionOptions.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.bleplx.adapter; - -import androidx.annotation.Nullable; - -import com.bleplx.adapter.utils.Constants.ConnectionPriority; - -public class ConnectionOptions { - - /** - * Whether to directly connect to the remote device (false) or to automatically connect as soon - * as the remote device becomes available (true). - */ - private final boolean autoConnect; - - /** - * Whether MTU size will be negotiated to this value. It is not guaranteed to get it after - * connection is successful. - */ - private final int requestMTU; - - /** - * Whether action will be taken to reset services cache. This option may be useful when a - * peripheral's firmware was updated and it's services/characteristics were - * added/removed/altered. {@link ...} - */ - private final RefreshGattMoment refreshGattMoment; - - /** - * Number of milliseconds after connection is automatically timed out. In case of race condition - * were connection is established right after timeout event, device will be disconnected - * immediately. Time out may happen earlier then specified due to OS specific behavior. - */ - @Nullable - private final Long timeoutInMillis; - - @ConnectionPriority - private final int connectionPriority; - - public ConnectionOptions(Boolean autoConnect, - int requestMTU, - RefreshGattMoment refreshGattMoment, - @Nullable Long timeoutInMillis, - int connectionPriority) { - this.autoConnect = autoConnect; - this.requestMTU = requestMTU; - this.refreshGattMoment = refreshGattMoment; - this.timeoutInMillis = timeoutInMillis; - this.connectionPriority = connectionPriority; - } - - public Boolean getAutoConnect() { - return autoConnect; - } - - public int getRequestMTU() { - return requestMTU; - } - - public RefreshGattMoment getRefreshGattMoment() { - return refreshGattMoment; - } - - @Nullable - public Long getTimeoutInMillis() { - return timeoutInMillis; - } - - @ConnectionPriority - public int getConnectionPriority() { - return connectionPriority; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/ConnectionState.java b/android/src/main/java/com/bleplx/adapter/ConnectionState.java deleted file mode 100644 index 56e0ca93d..000000000 --- a/android/src/main/java/com/bleplx/adapter/ConnectionState.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.bleplx.adapter; - -public enum ConnectionState { - - CONNECTING("connecting"), CONNECTED("connected"), DISCONNECTING("disconnecting"), DISCONNECTED("disconnected"); - - public final String value; - - ConnectionState(String value) { - this.value = value; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/Descriptor.java b/android/src/main/java/com/bleplx/adapter/Descriptor.java deleted file mode 100644 index 5057497d4..000000000 --- a/android/src/main/java/com/bleplx/adapter/Descriptor.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.bleplx.adapter; - -import android.bluetooth.BluetoothGattDescriptor; - -import androidx.annotation.NonNull; - -import com.bleplx.adapter.utils.ByteUtils; -import com.bleplx.adapter.utils.IdGenerator; -import com.bleplx.adapter.utils.IdGeneratorKey; -import com.polidea.rxandroidble2.internal.RxBleLog; - -import java.util.UUID; - -/** - * @noinspection unused - */ -public class Descriptor { - final private int characteristicId; - final private int serviceId; - final private UUID characteristicUuid; - final private UUID serviceUuid; - final private String deviceId; - final private BluetoothGattDescriptor descriptor; - final private int id; - final private UUID uuid; - private byte[] value = null; - - public Descriptor(@NonNull Characteristic characteristic, @NonNull BluetoothGattDescriptor gattDescriptor) { - this.characteristicId = characteristic.getId(); - this.characteristicUuid = characteristic.getUuid(); - this.serviceId = characteristic.getServiceID(); - this.serviceUuid = characteristic.getServiceUUID(); - this.descriptor = gattDescriptor; - this.deviceId = characteristic.getDeviceId(); - this.id = IdGenerator.getIdForKey(new IdGeneratorKey(deviceId, descriptor.getUuid(), characteristicId)); - this.uuid = gattDescriptor.getUuid(); - } - - //secondary constructor, not used by MBA itself, but which can be used by external plugins (eg. BLEmulator) - public Descriptor(int characteristicId, int serviceId, UUID characteristicUuid, UUID serviceUuid, String deviceId, BluetoothGattDescriptor descriptor, int id, UUID uuid) { - this.characteristicId = characteristicId; - this.serviceId = serviceId; - this.characteristicUuid = characteristicUuid; - this.serviceUuid = serviceUuid; - this.deviceId = deviceId; - this.descriptor = descriptor; - this.id = id; - this.uuid = uuid; - } - - public Descriptor(Descriptor other) { - characteristicUuid = other.characteristicUuid; - characteristicId = other.characteristicId; - serviceUuid = other.serviceUuid; - serviceId = other.serviceId; - deviceId = other.deviceId; - descriptor = other.descriptor; - id = other.id; - uuid = other.uuid; - if (other.value != null) value = other.value.clone(); - } - - public int getId() { - return id; - } - - public String getDeviceId() { - return deviceId; - } - - public int getCharacteristicId() { - return characteristicId; - } - - public int getServiceId() { - return serviceId; - } - - public UUID getCharacteristicUuid() { - return characteristicUuid; - } - - public UUID getServiceUuid() { - return serviceUuid; - } - - public UUID getUuid() { - return uuid; - } - - public byte[] getValue() { - return value; - } - - public void setValue(byte[] value) { - this.value = value; - } - - public void setValueFromCache() { - value = descriptor.getValue(); - } - - public BluetoothGattDescriptor getNativeDescriptor() { - return descriptor; - } - - public void logValue(String message, byte[] value) { - if (value == null) { - value = descriptor.getValue(); - } - String hexValue = value != null ? ByteUtils.bytesToHex(value) : "(null)"; - RxBleLog.v(message + - " Descriptor(uuid: " + descriptor.getUuid().toString() + - ", id: " + id + - ", value: " + hexValue + ")"); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/Device.java b/android/src/main/java/com/bleplx/adapter/Device.java deleted file mode 100755 index 253042be2..000000000 --- a/android/src/main/java/com/bleplx/adapter/Device.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.bleplx.adapter; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -public class Device { - - private String id; - private String name; - @Nullable - private Integer rssi; - @Nullable - private Integer mtu; - @Nullable - private List services; - - public Device(String id, String name) { - this.id = id; - this.name = name; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Nullable - public Integer getRssi() { - return rssi; - } - - public void setRssi(@Nullable Integer rssi) { - this.rssi = rssi; - } - - @Nullable - public Integer getMtu() { - return mtu; - } - - public void setMtu(@Nullable Integer mtu) { - this.mtu = mtu; - } - - @Nullable - public List getServices() { - return services; - } - - @Nullable - public List getServicesUUIDs() { - if (services == null) { - return null; - } - - List servicesUUIDs = new ArrayList<>(); - for (Service service : services) { - servicesUUIDs.add(service.getUuid()); - } - - return servicesUUIDs; - } - - public void setServices(@Nullable List services) { - this.services = services; - } - - @Nullable - public Service getServiceByUUID(@NonNull UUID uuid) { - if (services == null) { - return null; - } - - for (Service service : services) { - if (uuid.equals(service.getUuid())) - return service; - } - return null; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/OnErrorCallback.java b/android/src/main/java/com/bleplx/adapter/OnErrorCallback.java deleted file mode 100644 index a073aff4d..000000000 --- a/android/src/main/java/com/bleplx/adapter/OnErrorCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.bleplx.adapter; - -import com.bleplx.adapter.errors.BleError; - -public interface OnErrorCallback { - - void onError(BleError error); -} diff --git a/android/src/main/java/com/bleplx/adapter/OnEventCallback.java b/android/src/main/java/com/bleplx/adapter/OnEventCallback.java deleted file mode 100644 index bc6330c8e..000000000 --- a/android/src/main/java/com/bleplx/adapter/OnEventCallback.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.bleplx.adapter; - -public interface OnEventCallback { - - void onEvent(T data); -} diff --git a/android/src/main/java/com/bleplx/adapter/OnSuccessCallback.java b/android/src/main/java/com/bleplx/adapter/OnSuccessCallback.java deleted file mode 100644 index 27a396917..000000000 --- a/android/src/main/java/com/bleplx/adapter/OnSuccessCallback.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.bleplx.adapter; - -public interface OnSuccessCallback { - - void onSuccess(T data); -} diff --git a/android/src/main/java/com/bleplx/adapter/RefreshGattMoment.java b/android/src/main/java/com/bleplx/adapter/RefreshGattMoment.java deleted file mode 100755 index eafefc071..000000000 --- a/android/src/main/java/com/bleplx/adapter/RefreshGattMoment.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.bleplx.adapter; - - -public enum RefreshGattMoment { - - ON_CONNECTED("OnConnected"); - - final String name; - - RefreshGattMoment(String name) { - this.name = name; - } - - public static RefreshGattMoment getByName(String name) { - for (RefreshGattMoment refreshGattMoment : RefreshGattMoment.values()) { - if (refreshGattMoment.name.equals(name)) return refreshGattMoment; - } - return null; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/ScanResult.java b/android/src/main/java/com/bleplx/adapter/ScanResult.java deleted file mode 100644 index 53eac5a86..000000000 --- a/android/src/main/java/com/bleplx/adapter/ScanResult.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.bleplx.adapter; - - -import androidx.annotation.Nullable; - -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; - -/** - * @noinspection unused - */ -public class ScanResult { - - private String deviceId; - private String deviceName; - private int rssi; - private int mtu; - private boolean isConnectable; - @Nullable - private UUID[] overflowServiceUUIDs; - private AdvertisementData advertisementData; - - public ScanResult(String deviceId, String deviceName, int rssi, int mtu, boolean isConnectable, @Nullable UUID[] overflowServiceUUIDs, AdvertisementData advertisementData) { - this.deviceId = deviceId; - this.deviceName = deviceName; - this.rssi = rssi; - this.mtu = mtu; - this.isConnectable = isConnectable; - this.overflowServiceUUIDs = overflowServiceUUIDs; - this.advertisementData = advertisementData; - } - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getDeviceName() { - return deviceName; - } - - public void setDeviceName(String deviceName) { - this.deviceName = deviceName; - } - - public int getRssi() { - return rssi; - } - - public void setRssi(int rssi) { - this.rssi = rssi; - } - - public int getMtu() { - return mtu; - } - - public void setMtu(int mtu) { - this.mtu = mtu; - } - - public boolean isConnectable() { - return isConnectable; - } - - public void setConnectable(boolean connectable) { - isConnectable = connectable; - } - - public UUID[] getOverflowServiceUUIDs() { - return overflowServiceUUIDs; - } - - public void setOverflowServiceUUIDs(@Nullable UUID[] overflowServiceUUIDs) { - this.overflowServiceUUIDs = overflowServiceUUIDs; - } - - public AdvertisementData getAdvertisementData() { - return advertisementData; - } - - public void setAdvertisementData(AdvertisementData advertisementData) { - this.advertisementData = advertisementData; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ScanResult that = (ScanResult) o; - - if (rssi != that.rssi) return false; - if (mtu != that.mtu) return false; - if (isConnectable != that.isConnectable) return false; - if (!deviceId.equals(that.deviceId)) return false; - if (!Objects.equals(deviceName, that.deviceName)) - return false; - // Probably incorrect - comparing Object[] arrays with Arrays.equals - if (!Arrays.equals(overflowServiceUUIDs, that.overflowServiceUUIDs)) return false; - return Objects.equals(advertisementData, that.advertisementData); - } - - @Override - public int hashCode() { - int result = deviceId.hashCode(); - result = 31 * result + (deviceName != null ? deviceName.hashCode() : 0); - result = 31 * result + rssi; - result = 31 * result + mtu; - result = 31 * result + (isConnectable ? 1 : 0); - result = 31 * result + Arrays.hashCode(overflowServiceUUIDs); - result = 31 * result + (advertisementData != null ? advertisementData.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "ScanResult{" + - "deviceId='" + deviceId + '\'' + - ", deviceName='" + deviceName + '\'' + - ", rssi=" + rssi + - ", mtu=" + mtu + - ", isConnectable=" + isConnectable + - ", overflowServiceUUIDs=" + Arrays.toString(overflowServiceUUIDs) + - ", advertisementData=" + advertisementData + - '}'; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/Service.java b/android/src/main/java/com/bleplx/adapter/Service.java deleted file mode 100755 index b23eaa2bd..000000000 --- a/android/src/main/java/com/bleplx/adapter/Service.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.bleplx.adapter; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattService; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * @noinspection unused - */ -public class Service { - - final private int id; - final private String deviceID; - final private BluetoothGattService btGattService; - - public Service(int id, String deviceID, BluetoothGattService btGattService) { - this.id = id; - this.deviceID = deviceID; - this.btGattService = btGattService; - } - - public int getId() { - return this.id; - } - - public UUID getUuid() { - return btGattService.getUuid(); - } - - public String getDeviceID() { - return deviceID; - } - - public boolean isPrimary() { - return btGattService.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY; - } - - @Nullable - public Characteristic getCharacteristicByUUID(@NonNull UUID uuid) { - BluetoothGattCharacteristic characteristic = btGattService.getCharacteristic(uuid); - if (characteristic == null) return null; - return new Characteristic(this, characteristic); - } - - public List getCharacteristics() { - ArrayList characteristics = new ArrayList<>(btGattService.getCharacteristics().size()); - for (BluetoothGattCharacteristic gattCharacteristic : btGattService.getCharacteristics()) { - characteristics.add(new Characteristic(this, gattCharacteristic)); - } - return characteristics; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/errors/BleError.java b/android/src/main/java/com/bleplx/adapter/errors/BleError.java deleted file mode 100755 index 802528c86..000000000 --- a/android/src/main/java/com/bleplx/adapter/errors/BleError.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.bleplx.adapter.errors; - - -public class BleError extends Throwable { - - public BleErrorCode errorCode; - public Integer androidCode; - public String reason; - public String deviceID; - public String serviceUUID; - public String characteristicUUID; - public String descriptorUUID; - public String internalMessage; - - public BleError(BleErrorCode errorCode, String reason, Integer androidCode) { - this.errorCode = errorCode; - this.reason = reason; - this.androidCode = androidCode; - } - - @Override - public String getMessage() { - return "Error code: " + errorCode + - ", android code: " + androidCode + - ", reason" + reason + - ", deviceId" + deviceID + - ", serviceUuid" + serviceUUID + - ", characteristicUuid" + characteristicUUID + - ", descriptorUuid" + descriptorUUID + - ", internalMessage" + internalMessage; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/errors/BleErrorCode.java b/android/src/main/java/com/bleplx/adapter/errors/BleErrorCode.java deleted file mode 100755 index fe8a511d2..000000000 --- a/android/src/main/java/com/bleplx/adapter/errors/BleErrorCode.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.bleplx.adapter.errors; - - -public enum BleErrorCode { - - UnknownError(0), - BluetoothManagerDestroyed(1), - OperationCancelled(2), - OperationTimedOut(3), - OperationStartFailed(4), - InvalidIdentifiers(5), - - BluetoothUnsupported(100), - BluetoothUnauthorized(101), - BluetoothPoweredOff(102), - BluetoothInUnknownState(103), - BluetoothResetting(104), - BluetoothStateChangeFailed(105), - - DeviceConnectionFailed(200), - DeviceDisconnected(201), - DeviceRSSIReadFailed(202), - DeviceAlreadyConnected(203), - DeviceNotFound(204), - DeviceNotConnected(205), - DeviceMTUChangeFailed(206), - - ServicesDiscoveryFailed(300), - IncludedServicesDiscoveryFailed(301), - ServiceNotFound(302), - ServicesNotDiscovered(303), - - CharacteristicsDiscoveryFailed(400), - CharacteristicWriteFailed(401), - CharacteristicReadFailed(402), - CharacteristicNotifyChangeFailed(403), - CharacteristicNotFound(404), - CharacteristicsNotDiscovered(405), - CharacteristicInvalidDataFormat(406), - - DescriptorsDiscoveryFailed(500), - DescriptorWriteFailed(501), - DescriptorReadFailed(502), - DescriptorNotFound(503), - DescriptorsNotDiscovered(504), - DescriptorInvalidDataFormat(505), - DescriptorWriteNotAllowed(506), - - ScanStartFailed(600), - LocationServicesDisabled(601); - - public final int code; - - BleErrorCode(int code) { - this.code = code; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/errors/BleErrorUtils.java b/android/src/main/java/com/bleplx/adapter/errors/BleErrorUtils.java deleted file mode 100755 index 998647c9d..000000000 --- a/android/src/main/java/com/bleplx/adapter/errors/BleErrorUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.bleplx.adapter.errors; - - -import androidx.annotation.NonNull; - -public class BleErrorUtils { - - public static BleError cancelled() { - return new BleError(BleErrorCode.OperationCancelled, null, null); - } - - static public BleError invalidIdentifiers(@NonNull String... identifiers) { - StringBuilder identifiersJoined = new StringBuilder(); - for (String identifier : identifiers) { - identifiersJoined.append(identifier).append(", "); - } - - BleError bleError = new BleError(BleErrorCode.InvalidIdentifiers, null, null); - bleError.internalMessage = identifiersJoined.toString(); - return bleError; - } - - static public BleError deviceNotFound(String uuid) { - BleError bleError = new BleError(BleErrorCode.DeviceNotFound, null, null); - bleError.deviceID = uuid; - return bleError; - } - - static public BleError deviceNotConnected(String uuid) { - BleError bleError = new BleError(BleErrorCode.DeviceNotConnected, null, null); - bleError.deviceID = uuid; - return bleError; - } - - static public BleError characteristicNotFound(String uuid) { - BleError bleError = new BleError(BleErrorCode.CharacteristicNotFound, null, null); - bleError.characteristicUUID = uuid; - return bleError; - } - - static public BleError invalidWriteDataForCharacteristic(String data, String uuid) { - BleError bleError = new BleError(BleErrorCode.CharacteristicInvalidDataFormat, null, null); - bleError.characteristicUUID = uuid; - bleError.internalMessage = data; - return bleError; - } - - static public BleError descriptorNotFound(String uuid) { - BleError bleError = new BleError(BleErrorCode.DescriptorNotFound, null, null); - bleError.descriptorUUID = uuid; - return bleError; - } - - static public BleError invalidWriteDataForDescriptor(String data, String uuid) { - BleError bleError = new BleError(BleErrorCode.DescriptorInvalidDataFormat, null, null); - bleError.descriptorUUID = uuid; - bleError.internalMessage = data; - return bleError; - } - - static public BleError descriptorWriteNotAllowed(String uuid) { - BleError bleError = new BleError(BleErrorCode.DescriptorWriteNotAllowed, null, null); - bleError.descriptorUUID = uuid; - return bleError; - } - - static public BleError serviceNotFound(String uuid) { - BleError bleError = new BleError(BleErrorCode.ServiceNotFound, null, null); - bleError.serviceUUID = uuid; - return bleError; - } - - static public BleError cannotMonitorCharacteristic(String reason, String deviceID, String serviceUUID, String characteristicUUID) { - BleError bleError = new BleError(BleErrorCode.CharacteristicNotifyChangeFailed, reason, null); - bleError.deviceID = deviceID; - bleError.serviceUUID = serviceUUID; - bleError.characteristicUUID = characteristicUUID; - return bleError; - } - - public static BleError deviceServicesNotDiscovered(String deviceID) { - BleError bleError = new BleError(BleErrorCode.ServicesNotDiscovered, null, null); - bleError.deviceID = deviceID; - return bleError; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/errors/ErrorConverter.java b/android/src/main/java/com/bleplx/adapter/errors/ErrorConverter.java deleted file mode 100755 index 96bb3c66d..000000000 --- a/android/src/main/java/com/bleplx/adapter/errors/ErrorConverter.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.bleplx.adapter.errors; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattService; - -import com.bleplx.adapter.Characteristic; -import com.bleplx.adapter.exceptions.CannotMonitorCharacteristicException; -import com.bleplx.adapter.utils.UUIDConverter; -import com.polidea.rxandroidble2.exceptions.BleAlreadyConnectedException; -import com.polidea.rxandroidble2.exceptions.BleCannotSetCharacteristicNotificationException; -import com.polidea.rxandroidble2.exceptions.BleCharacteristicNotFoundException; -import com.polidea.rxandroidble2.exceptions.BleConflictingNotificationAlreadySetException; -import com.polidea.rxandroidble2.exceptions.BleDisconnectedException; -import com.polidea.rxandroidble2.exceptions.BleGattCallbackTimeoutException; -import com.polidea.rxandroidble2.exceptions.BleGattCannotStartException; -import com.polidea.rxandroidble2.exceptions.BleGattCharacteristicException; -import com.polidea.rxandroidble2.exceptions.BleGattDescriptorException; -import com.polidea.rxandroidble2.exceptions.BleGattException; -import com.polidea.rxandroidble2.exceptions.BleGattOperationType; -import com.polidea.rxandroidble2.exceptions.BleScanException; -import com.polidea.rxandroidble2.exceptions.BleServiceNotFoundException; - -import java.util.UUID; -import java.util.concurrent.TimeoutException; - -public class ErrorConverter { - - public BleError toError(Throwable throwable) { - - // Custom exceptions ----------------------------------------------------------------------- - - if (throwable instanceof CannotMonitorCharacteristicException) { - CannotMonitorCharacteristicException exception = (CannotMonitorCharacteristicException) throwable; - Characteristic characteristic = exception.getCharacteristic(); - // TODO: Missing deviceID - return BleErrorUtils.cannotMonitorCharacteristic( - throwable.getMessage(), - null, - UUIDConverter.fromUUID(characteristic.getServiceUUID()), - UUIDConverter.fromUUID(characteristic.getUuid())); - } - - // RxSwift exceptions ---------------------------------------------------------------------- - - if (throwable instanceof TimeoutException) { - return new BleError(BleErrorCode.OperationTimedOut, throwable.getMessage(), null); - } - - // RxAndroidBle exceptions ----------------------------------------------------------------- - - if (throwable instanceof BleAlreadyConnectedException) { - // TODO: Missing deviceID - return new BleError(BleErrorCode.DeviceAlreadyConnected, throwable.getMessage(), null); - } - - if (throwable instanceof BleCannotSetCharacteristicNotificationException) { - BluetoothGattCharacteristic gattCharacteristic = ((BleCannotSetCharacteristicNotificationException) throwable).getBluetoothGattCharacteristic(); - BluetoothGattService gattService = gattCharacteristic.getService(); - // TODO: Missing deviceID - return BleErrorUtils.cannotMonitorCharacteristic( - throwable.getMessage(), - null, - UUIDConverter.fromUUID(gattService.getUuid()), - UUIDConverter.fromUUID(gattCharacteristic.getUuid())); - } - - - if (throwable instanceof BleCharacteristicNotFoundException) { - UUID uuid = ((BleCharacteristicNotFoundException) throwable).getCharacteristicUUID(); - BleError bleError = new BleError(BleErrorCode.CharacteristicNotFound, throwable.getMessage(), null); - bleError.characteristicUUID = UUIDConverter.fromUUID(uuid); - return bleError; - } - - if (throwable instanceof BleConflictingNotificationAlreadySetException) { - UUID characteristicUUID = ((BleConflictingNotificationAlreadySetException) throwable).getCharacteristicUuid(); - // TODO: Missing Service UUID and device ID - return BleErrorUtils.cannotMonitorCharacteristic(throwable.getMessage(), null, null, UUIDConverter.fromUUID(characteristicUUID)); - } - - if (throwable instanceof BleDisconnectedException) { - BleDisconnectedException bleDisconnectedException = (BleDisconnectedException) throwable; - BleError bleError = new BleError(BleErrorCode.DeviceDisconnected, throwable.getMessage(), bleDisconnectedException.state); - bleError.deviceID = bleDisconnectedException.bluetoothDeviceAddress; - return bleError; - } - - if (throwable instanceof BleScanException) { - return toError((BleScanException) throwable); - } - - if (throwable instanceof BleServiceNotFoundException) { - BleError bleError = new BleError(BleErrorCode.ServiceNotFound, throwable.getMessage(), null); - bleError.serviceUUID = UUIDConverter.fromUUID(((BleServiceNotFoundException) throwable).getServiceUUID()); - return bleError; - } - - // RxAndroidBle (GATT) exceptions ---------------------------------------------------------- - - if (throwable instanceof BleGattCallbackTimeoutException) { - return new BleError(BleErrorCode.OperationTimedOut, throwable.getMessage(), ((BleGattCallbackTimeoutException) throwable).getStatus()); - } - - if (throwable instanceof BleGattCannotStartException) { - return new BleError(BleErrorCode.OperationStartFailed, throwable.getMessage(), ((BleGattCannotStartException) throwable).getStatus()); - } - - if (throwable instanceof BleGattCharacteristicException) { - BleGattCharacteristicException exception = (BleGattCharacteristicException) throwable; - int code = exception.getStatus(); - BleGattOperationType operationType = exception.getBleGattOperationType(); - - return toError(code, - throwable.getMessage(), - operationType, - exception.getMacAddress(), - UUIDConverter.fromUUID(exception.characteristic.getService().getUuid()), - UUIDConverter.fromUUID(exception.characteristic.getUuid()), - null); - } - - if (throwable instanceof BleGattDescriptorException) { - BleGattDescriptorException exception = (BleGattDescriptorException) throwable; - int code = exception.getStatus(); - BleGattOperationType operationType = exception.getBleGattOperationType(); - - return toError(code, - throwable.getMessage(), - operationType, - exception.getMacAddress(), - UUIDConverter.fromUUID(exception.descriptor.getCharacteristic().getService().getUuid()), - UUIDConverter.fromUUID(exception.descriptor.getCharacteristic().getUuid()), - UUIDConverter.fromUUID(exception.descriptor.getUuid())); - } - - if (throwable instanceof BleGattException) { - BleGattException exception = (BleGattException) throwable; - int code = exception.getStatus(); - BleGattOperationType operationType = exception.getBleGattOperationType(); - - return toError(code, - throwable.getMessage(), - operationType, - exception.getMacAddress(), - null, - null, - null); - } - - return new BleError(BleErrorCode.UnknownError, throwable.toString(), null); - } - - private BleError toError(int code, String message, BleGattOperationType operationType, String deviceID, String serviceUUID, String characteristicUUID, String descriptorUUID) { - if (BleGattOperationType.CONNECTION_STATE == operationType) { - BleError bleError = new BleError(BleErrorCode.DeviceDisconnected, message, code); - bleError.deviceID = deviceID; - return bleError; - } else if (BleGattOperationType.SERVICE_DISCOVERY == operationType) { - BleError bleError = new BleError(BleErrorCode.ServicesDiscoveryFailed, message, code); - bleError.deviceID = deviceID; - return bleError; - } else if (BleGattOperationType.CHARACTERISTIC_READ == operationType || BleGattOperationType.CHARACTERISTIC_CHANGED == operationType) { - BleError bleError = new BleError(BleErrorCode.CharacteristicReadFailed, message, code); - bleError.deviceID = deviceID; - bleError.serviceUUID = serviceUUID; - bleError.characteristicUUID = characteristicUUID; - return bleError; - } else if (BleGattOperationType.CHARACTERISTIC_WRITE == operationType || BleGattOperationType.CHARACTERISTIC_LONG_WRITE == operationType || BleGattOperationType.RELIABLE_WRITE_COMPLETED == operationType) { - BleError bleError = new BleError(BleErrorCode.CharacteristicWriteFailed, message, code); - bleError.deviceID = deviceID; - bleError.serviceUUID = serviceUUID; - bleError.characteristicUUID = characteristicUUID; - return bleError; - } else if (BleGattOperationType.DESCRIPTOR_READ == operationType) { - BleError bleError = new BleError(BleErrorCode.DescriptorReadFailed, message, code); - bleError.deviceID = deviceID; - bleError.serviceUUID = serviceUUID; - bleError.characteristicUUID = characteristicUUID; - bleError.descriptorUUID = descriptorUUID; - return bleError; - } else if (BleGattOperationType.DESCRIPTOR_WRITE == operationType) { - BleError bleError = new BleError(BleErrorCode.DescriptorWriteFailed, message, code); - bleError.deviceID = deviceID; - bleError.serviceUUID = serviceUUID; - bleError.characteristicUUID = characteristicUUID; - bleError.descriptorUUID = descriptorUUID; - return bleError; - } else if (BleGattOperationType.READ_RSSI == operationType) { - BleError bleError = new BleError(BleErrorCode.DeviceRSSIReadFailed, message, code); - bleError.deviceID = deviceID; - return bleError; - } else if (BleGattOperationType.ON_MTU_CHANGED == operationType) { - BleError bleError = new BleError(BleErrorCode.DeviceMTUChangeFailed, message, code); - bleError.deviceID = deviceID; - return bleError; - } else if (BleGattOperationType.CONNECTION_PRIORITY_CHANGE == operationType) { - // TODO: Handle? - } - - return new BleError(BleErrorCode.UnknownError, message, code); - } - - private BleError toError(BleScanException bleScanException) { - final int reason = bleScanException.getReason(); - switch (reason) { - case BleScanException.BLUETOOTH_DISABLED: - return new BleError(BleErrorCode.BluetoothPoweredOff, bleScanException.getMessage(), null); - case BleScanException.BLUETOOTH_NOT_AVAILABLE: - return new BleError(BleErrorCode.BluetoothUnsupported, bleScanException.getMessage(), null); - case BleScanException.LOCATION_PERMISSION_MISSING: - return new BleError(BleErrorCode.BluetoothUnauthorized, bleScanException.getMessage(), null); - case BleScanException.LOCATION_SERVICES_DISABLED: - return new BleError(BleErrorCode.LocationServicesDisabled, bleScanException.getMessage(), null); - case BleScanException.BLUETOOTH_CANNOT_START: - default: - return new BleError(BleErrorCode.ScanStartFailed, bleScanException.getMessage(), null); - } - } -} diff --git a/android/src/main/java/com/bleplx/adapter/exceptions/CannotMonitorCharacteristicException.java b/android/src/main/java/com/bleplx/adapter/exceptions/CannotMonitorCharacteristicException.java deleted file mode 100755 index bc94b7f81..000000000 --- a/android/src/main/java/com/bleplx/adapter/exceptions/CannotMonitorCharacteristicException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.bleplx.adapter.exceptions; - -import com.bleplx.adapter.Characteristic; - -public class CannotMonitorCharacteristicException extends RuntimeException { - private Characteristic characteristic; - - public CannotMonitorCharacteristicException(Characteristic characteristic) { - this.characteristic = characteristic; - } - - public Characteristic getCharacteristic() { - return characteristic; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/Base64Converter.java b/android/src/main/java/com/bleplx/adapter/utils/Base64Converter.java deleted file mode 100755 index 814fe773d..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/Base64Converter.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.bleplx.adapter.utils; - -import android.util.Base64; - -public class Base64Converter { - public static String encode(byte[] bytes) { - return Base64.encodeToString(bytes, Base64.NO_WRAP); - } - - public static byte[] decode(String base64) { - return Base64.decode(base64, Base64.NO_WRAP); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/ByteUtils.java b/android/src/main/java/com/bleplx/adapter/utils/ByteUtils.java deleted file mode 100644 index 111ec29a4..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/ByteUtils.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.bleplx.adapter.utils; - -public class ByteUtils { - private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - public static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/Constants.java b/android/src/main/java/com/bleplx/adapter/utils/Constants.java deleted file mode 100755 index 048cefe92..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/Constants.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.bleplx.adapter.utils; - - -import androidx.annotation.IntDef; -import androidx.annotation.StringDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.UUID; - -public interface Constants { - - @StringDef({ - BluetoothState.UNKNOWN, - BluetoothState.RESETTING, - BluetoothState.UNSUPPORTED, - BluetoothState.UNAUTHORIZED, - BluetoothState.POWERED_OFF, - BluetoothState.POWERED_ON} - ) - @Retention(RetentionPolicy.SOURCE) - @interface BluetoothState { - - String UNKNOWN = "Unknown"; - String RESETTING = "Resetting"; - String UNSUPPORTED = "Unsupported"; - String UNAUTHORIZED = "Unauthorized"; - String POWERED_OFF = "PoweredOff"; - String POWERED_ON = "PoweredOn"; - } - - @StringDef({ - BluetoothLogLevel.NONE, - BluetoothLogLevel.VERBOSE, - BluetoothLogLevel.DEBUG, - BluetoothLogLevel.INFO, - BluetoothLogLevel.WARNING, - BluetoothLogLevel.ERROR} - ) - @Retention(RetentionPolicy.SOURCE) - @interface BluetoothLogLevel { - - String NONE = "None"; - String VERBOSE = "Verbose"; - String DEBUG = "Debug"; - String INFO = "Info"; - String WARNING = "Warning"; - String ERROR = "Error"; - } - - @IntDef({ - ConnectionPriority.BALANCED, - ConnectionPriority.HIGH, - ConnectionPriority.LOW_POWER} - ) - @Retention(RetentionPolicy.SOURCE) - @interface ConnectionPriority { - - int BALANCED = 0; - int HIGH = 1; - int LOW_POWER = 2; - } - - int MINIMUM_MTU = 23; - UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/DisposableMap.java b/android/src/main/java/com/bleplx/adapter/utils/DisposableMap.java deleted file mode 100755 index 83e947296..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/DisposableMap.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.bleplx.adapter.utils; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import io.reactivex.disposables.Disposable; - -public class DisposableMap { - - final private Map subscriptions = new HashMap<>(); - - public synchronized void replaceSubscription(String key, Disposable subscription) { - Disposable oldSubscription = subscriptions.put(key, subscription); - if (oldSubscription != null && !oldSubscription.isDisposed()) { - oldSubscription.dispose(); - } - } - - public synchronized boolean removeSubscription(String key) { - Disposable subscription = subscriptions.remove(key); - if (subscription == null) return false; - if (!subscription.isDisposed()) { - subscription.dispose(); - } - return true; - } - - public synchronized void removeAllSubscriptions() { - Iterator> it = subscriptions.entrySet().iterator(); - while (it.hasNext()) { - Disposable subscription = it.next().getValue(); - it.remove(); - if (!subscription.isDisposed()) { - subscription.dispose(); - } - } - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/IdGenerator.java b/android/src/main/java/com/bleplx/adapter/utils/IdGenerator.java deleted file mode 100755 index 116124e10..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/IdGenerator.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.bleplx.adapter.utils; - -import java.util.HashMap; - -public class IdGenerator { - private static HashMap idMap = new HashMap<>(); - private static int nextKey = 0; - - public static int getIdForKey(IdGeneratorKey idGeneratorKey) { - Integer id = idMap.get(idGeneratorKey); - if (id != null) { - return id; - } - idMap.put(idGeneratorKey, ++nextKey); - return nextKey; - } - - public static void clear() { - idMap.clear(); - nextKey = 0; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/IdGeneratorKey.java b/android/src/main/java/com/bleplx/adapter/utils/IdGeneratorKey.java deleted file mode 100755 index 721b9f6f1..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/IdGeneratorKey.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.bleplx.adapter.utils; - - -import java.util.UUID; - -public class IdGeneratorKey { - - private final String deviceAddress; - private final UUID uuid; - private final int id; - - public IdGeneratorKey(String deviceAddress, UUID uuid, int id) { - this.deviceAddress = deviceAddress; - this.uuid = uuid; - this.id = id; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - IdGeneratorKey that = (IdGeneratorKey) o; - - if (id != that.id) return false; - if (!deviceAddress.equals(that.deviceAddress)) return false; - return uuid.equals(that.uuid); - } - - @Override - public int hashCode() { - int result = deviceAddress.hashCode(); - result = 31 * result + uuid.hashCode(); - result = 31 * result + id; - return result; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/LogLevel.java b/android/src/main/java/com/bleplx/adapter/utils/LogLevel.java deleted file mode 100755 index eb249fd1b..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/LogLevel.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.bleplx.adapter.utils; - - -import com.polidea.rxandroidble2.internal.RxBleLog; - -public class LogLevel { - - @RxBleLog.LogLevel - public static int toLogLevel(String logLevel) { - switch (logLevel) { - case Constants.BluetoothLogLevel.VERBOSE: - return RxBleLog.VERBOSE; - case Constants.BluetoothLogLevel.DEBUG: - return RxBleLog.DEBUG; - case Constants.BluetoothLogLevel.INFO: - return RxBleLog.INFO; - case Constants.BluetoothLogLevel.WARNING: - return RxBleLog.WARN; - case Constants.BluetoothLogLevel.ERROR: - return RxBleLog.ERROR; - case Constants.BluetoothLogLevel.NONE: - // fallthrough - default: - return RxBleLog.NONE; - } - } - - @Constants.BluetoothLogLevel - public static String fromLogLevel(int logLevel) { - switch (logLevel) { - case RxBleLog.VERBOSE: - return Constants.BluetoothLogLevel.VERBOSE; - case RxBleLog.DEBUG: - return Constants.BluetoothLogLevel.DEBUG; - case RxBleLog.INFO: - return Constants.BluetoothLogLevel.INFO; - case RxBleLog.WARN: - return Constants.BluetoothLogLevel.WARNING; - case RxBleLog.ERROR: - return Constants.BluetoothLogLevel.ERROR; - case RxBleLog.NONE: - // fallthrough - default: - return Constants.BluetoothLogLevel.NONE; - } - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/RefreshGattCustomOperation.java b/android/src/main/java/com/bleplx/adapter/utils/RefreshGattCustomOperation.java deleted file mode 100755 index 68f7fff07..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/RefreshGattCustomOperation.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.bleplx.adapter.utils; - -import android.bluetooth.BluetoothGatt; - -import androidx.annotation.NonNull; - -import com.polidea.rxandroidble2.RxBleCustomOperation; -import com.polidea.rxandroidble2.internal.RxBleLog; -import com.polidea.rxandroidble2.internal.connection.RxBleGattCallback; - -import java.lang.reflect.Method; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Observable; -import io.reactivex.Scheduler; - -public class RefreshGattCustomOperation implements RxBleCustomOperation { - - /** - * @noinspection unchecked, JavaReflectionMemberAccess, DataFlowIssue - */ - @NonNull - @Override - public Observable asObservable( - final BluetoothGatt bluetoothGatt, - final RxBleGattCallback rxBleGattCallback, - final Scheduler scheduler - ) { - return Observable.ambArray( - Observable.fromCallable(() -> { - boolean success = false; - try { - Method bluetoothGattRefreshFunction = bluetoothGatt.getClass().getMethod("refresh"); - - success = (Boolean) bluetoothGattRefreshFunction.invoke(bluetoothGatt); - - if (!success) RxBleLog.d("BluetoothGatt.refresh() returned false"); - } catch (Exception e) { - RxBleLog.d(e, "Could not call function BluetoothGatt.refresh()"); - } - - RxBleLog.i("Calling BluetoothGatt.refresh() status: %s", success ? "Success" : "Failure"); - return success; - }) - .subscribeOn(scheduler) - .delay(1, TimeUnit.SECONDS, scheduler), - rxBleGattCallback.observeDisconnect() - ); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/SafeExecutor.java b/android/src/main/java/com/bleplx/adapter/utils/SafeExecutor.java deleted file mode 100644 index fa19f4e37..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/SafeExecutor.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.bleplx.adapter.utils; - -import androidx.annotation.Nullable; - -import com.bleplx.adapter.OnErrorCallback; -import com.bleplx.adapter.OnSuccessCallback; -import com.bleplx.adapter.errors.BleError; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class SafeExecutor { - - private final OnSuccessCallback successCallback; - private final OnErrorCallback errorCallback; - private final AtomicBoolean wasExecuted = new AtomicBoolean(false); - - public SafeExecutor(@Nullable OnSuccessCallback successCallback, @Nullable OnErrorCallback errorCallback) { - this.successCallback = successCallback; - this.errorCallback = errorCallback; - } - - public void success(T data) { - if (wasExecuted.compareAndSet(false, true) && successCallback != null) { - successCallback.onSuccess(data); - } - } - - public void error(BleError error) { - if (wasExecuted.compareAndSet(false, true) && errorCallback != null) { - errorCallback.onError(error); - } - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/ServiceFactory.java b/android/src/main/java/com/bleplx/adapter/utils/ServiceFactory.java deleted file mode 100644 index b4b2f9f0c..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/ServiceFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.bleplx.adapter.utils; - -import android.bluetooth.BluetoothGattService; - -import com.bleplx.adapter.Service; - -public class ServiceFactory { - - public Service create(String deviceId, BluetoothGattService btGattService) { - return new Service( - IdGenerator.getIdForKey(new IdGeneratorKey(deviceId, btGattService.getUuid(), btGattService.getInstanceId())), - deviceId, - btGattService - ); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/UUIDConverter.java b/android/src/main/java/com/bleplx/adapter/utils/UUIDConverter.java deleted file mode 100755 index 96247f8ce..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/UUIDConverter.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.bleplx.adapter.utils; - - -import java.util.UUID; - -public class UUIDConverter { - - private static String baseUUIDPrefix = "0000"; - private static String baseUUIDSuffix = "-0000-1000-8000-00805F9B34FB"; - - public static UUID convert(String sUUID) { - if (sUUID == null) return null; - - if (sUUID.length() == 4) { - sUUID = baseUUIDPrefix + sUUID + baseUUIDSuffix; - } else if (sUUID.length() == 8) { - sUUID = sUUID + baseUUIDSuffix; - } - - try { - return UUID.fromString(sUUID); - } catch (Throwable e) { - return null; - } - } - - public static UUID[] convert(String... sUUIDs) { - UUID[] UUIDs = new UUID[sUUIDs.length]; - for (int i = 0; i < sUUIDs.length; i++) { - - if (sUUIDs[i] == null) return null; - - if (sUUIDs[i].length() == 4) { - sUUIDs[i] = baseUUIDPrefix + sUUIDs[i] + baseUUIDSuffix; - } else if (sUUIDs[i].length() == 8) { - sUUIDs[i] = sUUIDs[i] + baseUUIDSuffix; - } - - try { - UUIDs[i] = UUID.fromString(sUUIDs[i]); - } catch (Throwable e) { - return null; - } - } - return UUIDs; - } - - public static String fromUUID(UUID uuid) { - return uuid.toString().toLowerCase(); - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/mapper/RxBleDeviceToDeviceMapper.java b/android/src/main/java/com/bleplx/adapter/utils/mapper/RxBleDeviceToDeviceMapper.java deleted file mode 100644 index 8e943e195..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/mapper/RxBleDeviceToDeviceMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.bleplx.adapter.utils.mapper; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bleplx.adapter.Device; -import com.polidea.rxandroidble2.RxBleConnection; -import com.polidea.rxandroidble2.RxBleDevice; - -public class RxBleDeviceToDeviceMapper { - - public Device map(@NonNull RxBleDevice rxDevice, @Nullable RxBleConnection connection) { - Device device = new Device(rxDevice.getMacAddress(), rxDevice.getName()); - if (connection != null) { - device.setMtu(connection.getMtu()); - } - return device; - } -} diff --git a/android/src/main/java/com/bleplx/adapter/utils/mapper/RxScanResultToScanResultMapper.java b/android/src/main/java/com/bleplx/adapter/utils/mapper/RxScanResultToScanResultMapper.java deleted file mode 100644 index 2cf44175a..000000000 --- a/android/src/main/java/com/bleplx/adapter/utils/mapper/RxScanResultToScanResultMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.bleplx.adapter.utils.mapper; - -import static com.polidea.rxandroidble2.scan.IsConnectable.CONNECTABLE; - -import com.bleplx.adapter.AdvertisementData; -import com.bleplx.adapter.ScanResult; -import com.bleplx.adapter.utils.Constants; - -public class RxScanResultToScanResultMapper { - - public ScanResult map(com.polidea.rxandroidble2.scan.ScanResult rxScanResult) { - return new ScanResult( - rxScanResult.getBleDevice().getMacAddress(), - rxScanResult.getBleDevice().getName(), - rxScanResult.getRssi(), - Constants.MINIMUM_MTU, - rxScanResult.isConnectable() == CONNECTABLE, - null, //overflowServiceUUIDs are not available on Android - AdvertisementData.parseScanResponseData(rxScanResult.getScanRecord().getBytes()) - ); - } -} diff --git a/android/src/main/java/com/bleplx/converter/BleErrorToJsObjectConverter.java b/android/src/main/java/com/bleplx/converter/BleErrorToJsObjectConverter.java deleted file mode 100644 index 370f13524..000000000 --- a/android/src/main/java/com/bleplx/converter/BleErrorToJsObjectConverter.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.bleplx.converter; - -import com.bleplx.adapter.errors.BleError; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.WritableArray; - -public class BleErrorToJsObjectConverter { - - public ReadableArray toJSCallback(BleError error) { - WritableArray array = Arguments.createArray(); - array.pushString(toJs(error)); - array.pushNull(); - return array; - } - - public String toJs(BleError error) { - WritableArray array = Arguments.createArray(); - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("{"); - - stringBuilder.append("\"errorCode\":"); - stringBuilder.append(error.errorCode.code); - - stringBuilder.append(",\"attErrorCode\":"); - if (error.androidCode == null || error.androidCode >= 0x80 || error.androidCode < 0) { - stringBuilder.append("null"); - } else { - stringBuilder.append(error.androidCode.intValue()); - } - - stringBuilder.append(",\"iosErrorCode\": null"); - - stringBuilder.append(",\"androidErrorCode\":"); - if (error.androidCode == null || error.androidCode < 0x80) { - stringBuilder.append("null"); - } else { - stringBuilder.append(error.androidCode.intValue()); - } - - appendString(stringBuilder, "reason", error.reason); - appendString(stringBuilder, "deviceID", error.deviceID); - appendString(stringBuilder, "serviceUUID", error.serviceUUID); - appendString(stringBuilder, "characteristicUUID", error.characteristicUUID); - appendString(stringBuilder, "descriptorUUID", error.descriptorUUID); - appendString(stringBuilder, "internalMessage", error.internalMessage); - - stringBuilder.append("}"); - - return stringBuilder.toString(); - } - - private void appendString(StringBuilder stringBuilder, String key, String value) { - stringBuilder.append(",\""); - stringBuilder.append(key); - stringBuilder.append("\":"); - if (value == null) { - stringBuilder.append("null"); - } else { - stringBuilder.append("\""); - stringBuilder.append(value); - stringBuilder.append("\""); - } - } -} diff --git a/android/src/main/java/com/bleplx/converter/CharacteristicToJsObjectConverter.java b/android/src/main/java/com/bleplx/converter/CharacteristicToJsObjectConverter.java deleted file mode 100644 index 270b4f62e..000000000 --- a/android/src/main/java/com/bleplx/converter/CharacteristicToJsObjectConverter.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.bleplx.converter; - -import com.bleplx.adapter.Characteristic; -import com.bleplx.adapter.utils.Base64Converter; -import com.bleplx.adapter.utils.UUIDConverter; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; - -public class CharacteristicToJsObjectConverter extends JSObjectConverter { - - private interface Metadata { - String ID = "id"; - String UUID = "uuid"; - String SERVICE_ID = "serviceID"; - String SERVICE_UUID = "serviceUUID"; - String DEVICE_ID = "deviceID"; - String IS_READABLE = "isReadable"; - String IS_WRITABLE_WITH_RESPONSE = "isWritableWithResponse"; - String IS_WRITABLE_WITHOUT_RESPONSE = "isWritableWithoutResponse"; - String IS_NOTIFIABLE = "isNotifiable"; - String IS_NOTIFYING = "isNotifying"; - String IS_INDICATABLE = "isIndicatable"; - String VALUE = "value"; - } - - @Override - public WritableMap toJSObject(Characteristic characteristic) { - WritableMap js = Arguments.createMap(); - - js.putInt(Metadata.ID, characteristic.getId()); - js.putString(Metadata.UUID, UUIDConverter.fromUUID(characteristic.getUuid())); - js.putInt(Metadata.SERVICE_ID, characteristic.getServiceID()); - js.putString(Metadata.SERVICE_UUID, UUIDConverter.fromUUID(characteristic.getServiceUUID())); - js.putString(Metadata.DEVICE_ID, characteristic.getDeviceId()); - js.putBoolean(Metadata.IS_READABLE, characteristic.isReadable()); - js.putBoolean(Metadata.IS_WRITABLE_WITH_RESPONSE, characteristic.isWritableWithResponse()); - js.putBoolean(Metadata.IS_WRITABLE_WITHOUT_RESPONSE, characteristic.isWritableWithoutResponse()); - js.putBoolean(Metadata.IS_NOTIFIABLE, characteristic.isNotifiable()); - js.putBoolean(Metadata.IS_INDICATABLE, characteristic.isIndicatable()); - js.putBoolean(Metadata.IS_NOTIFYING, characteristic.isNotifying()); - js.putString(Metadata.VALUE, - characteristic.getValue() != null ? - Base64Converter.encode(characteristic.getValue()) : null); - return js; - } -} diff --git a/android/src/main/java/com/bleplx/converter/DescriptorToJsObjectConverter.java b/android/src/main/java/com/bleplx/converter/DescriptorToJsObjectConverter.java deleted file mode 100644 index 05af9acb0..000000000 --- a/android/src/main/java/com/bleplx/converter/DescriptorToJsObjectConverter.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.bleplx.converter; - -import com.bleplx.adapter.Descriptor; -import com.bleplx.adapter.utils.Base64Converter; -import com.bleplx.adapter.utils.UUIDConverter; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; - -public class DescriptorToJsObjectConverter extends JSObjectConverter { - - private interface Metadata { - String ID = "id"; - String UUID = "uuid"; - String CHARACTERISTIC_UUID = "characteristicUUID"; - String CHARACTERISTIC_ID = "characteristicID"; - String SERVICE_ID = "serviceID"; - String SERVICE_UUID = "serviceUUID"; - String DEVICE_ID = "deviceID"; - String VALUE = "value"; - } - - @Override - public WritableMap toJSObject(Descriptor descriptor) { - WritableMap js = Arguments.createMap(); - js.putInt(Metadata.ID, descriptor.getId()); - js.putString(Metadata.UUID, UUIDConverter.fromUUID(descriptor.getUuid())); - js.putInt(Metadata.CHARACTERISTIC_ID, descriptor.getCharacteristicId()); - js.putString(Metadata.CHARACTERISTIC_UUID, UUIDConverter.fromUUID(descriptor.getCharacteristicUuid())); - js.putInt(Metadata.SERVICE_ID, descriptor.getServiceId()); - js.putString(Metadata.SERVICE_UUID, UUIDConverter.fromUUID(descriptor.getServiceUuid())); - js.putString(Metadata.DEVICE_ID, descriptor.getDeviceId()); - - if (descriptor.getValue() == null) { - descriptor.setValueFromCache(); - } - js.putString(Metadata.VALUE, descriptor.getValue() != null ? Base64Converter.encode(descriptor.getValue()) : null); - return js; - } -} diff --git a/android/src/main/java/com/bleplx/converter/DeviceToJsObjectConverter.java b/android/src/main/java/com/bleplx/converter/DeviceToJsObjectConverter.java deleted file mode 100644 index 1c5acdb61..000000000 --- a/android/src/main/java/com/bleplx/converter/DeviceToJsObjectConverter.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.bleplx.converter; - -import android.util.Log; - -import com.bleplx.adapter.Device; -import com.bleplx.utils.ReadableArrayConverter; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.WritableMap; - -import java.util.List; -import java.util.UUID; - -public class DeviceToJsObjectConverter extends JSObjectConverter { - - private interface Metadata { - String ID = "id"; - String NAME = "name"; - String RSSI = "rssi"; - String MTU = "mtu"; - - String MANUFACTURER_DATA = "manufacturerData"; - String SERVICE_DATA = "serviceData"; - String SERVICE_UUIDS = "serviceUUIDs"; - String LOCAL_NAME = "localName"; - String TX_POWER_LEVEL = "txPowerLevel"; - String SOLICITED_SERVICE_UUIDS = "solicitedServiceUUIDs"; - String IS_CONNECTABLE = "isConnectable"; - String OVERFLOW_SERVICE_UUIDS = "overflowServiceUUIDs"; - } - - - @Override - public WritableMap toJSObject(Device value) { - WritableMap result = Arguments.createMap(); - result.putString(Metadata.ID, value.getId()); - result.putString(Metadata.NAME, value.getName()); - if (value.getRssi() != null) { - result.putInt(Metadata.RSSI, value.getRssi()); - } else { - result.putNull(Metadata.RSSI); - } - if (value.getMtu() != null) { - result.putInt(Metadata.MTU, value.getMtu()); - } else { - result.putNull(Metadata.MTU); - } - - if(value.getServices() != null) { - result.putArray(Metadata.SERVICE_UUIDS, ReadableArrayConverter.toReadableArray(value.getServicesUUIDs())); - } else { - result.putNull(Metadata.SERVICE_UUIDS); - } - - // Advertisement data is not set - result.putNull(Metadata.MANUFACTURER_DATA); - result.putNull(Metadata.SERVICE_DATA); - result.putNull(Metadata.LOCAL_NAME); - result.putNull(Metadata.TX_POWER_LEVEL); - result.putNull(Metadata.SOLICITED_SERVICE_UUIDS); - result.putNull(Metadata.IS_CONNECTABLE); - result.putNull(Metadata.OVERFLOW_SERVICE_UUIDS); - - return result; - } -} diff --git a/android/src/main/java/com/bleplx/converter/JSObjectConverter.java b/android/src/main/java/com/bleplx/converter/JSObjectConverter.java deleted file mode 100644 index d72130c08..000000000 --- a/android/src/main/java/com/bleplx/converter/JSObjectConverter.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.bleplx.converter; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; - -abstract class JSObjectConverter { - - abstract public WritableMap toJSObject(T value); - - public WritableArray toJSCallback(T value) { - WritableArray array = Arguments.createArray(); - array.pushNull(); - array.pushMap(toJSObject(value)); - return array; - } -} diff --git a/android/src/main/java/com/bleplx/converter/ScanResultToJsObjectConverter.java b/android/src/main/java/com/bleplx/converter/ScanResultToJsObjectConverter.java deleted file mode 100644 index 1482f0292..000000000 --- a/android/src/main/java/com/bleplx/converter/ScanResultToJsObjectConverter.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.bleplx.converter; - -import androidx.annotation.NonNull; - -import com.bleplx.adapter.AdvertisementData; -import com.bleplx.adapter.ScanResult; -import com.bleplx.adapter.utils.Base64Converter; -import com.bleplx.adapter.utils.UUIDConverter; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; - -import java.util.Map; -import java.util.UUID; - -public class ScanResultToJsObjectConverter extends JSObjectConverter { - - interface Metadata { - String ID = "id"; - String NAME = "name"; - String RSSI = "rssi"; - String MTU = "mtu"; - - String MANUFACTURER_DATA = "manufacturerData"; - String SERVICE_DATA = "serviceData"; - String SERVICE_UUIDS = "serviceUUIDs"; - String LOCAL_NAME = "localName"; - String TX_POWER_LEVEL = "txPowerLevel"; - String SOLICITED_SERVICE_UUIDS = "solicitedServiceUUIDs"; - String RAW_SCAN_RECORD = "rawScanRecord"; - String IS_CONNECTABLE = "isConnectable"; - String OVERFLOW_SERVICE_UUIDS = "overflowServiceUUIDs"; - } - - @Override - public WritableMap toJSObject(@NonNull ScanResult scanResult) { - WritableMap result = Arguments.createMap(); - result.putString(Metadata.ID, scanResult.getDeviceId()); - result.putString(Metadata.NAME, scanResult.getDeviceName()); - result.putInt(Metadata.RSSI, scanResult.getRssi()); - result.putInt(Metadata.MTU, scanResult.getMtu()); - result.putBoolean(Metadata.IS_CONNECTABLE, scanResult.isConnectable()); - - AdvertisementData advData = scanResult.getAdvertisementData(); - result.putString(Metadata.MANUFACTURER_DATA, - advData.getManufacturerData() != null ? - Base64Converter.encode(advData.getManufacturerData()) : null); - - if (advData.getServiceData() != null) { - WritableMap serviceData = Arguments.createMap(); - for (Map.Entry entry : advData.getServiceData().entrySet()) { - serviceData.putString(UUIDConverter.fromUUID(entry.getKey()), - Base64Converter.encode(entry.getValue())); - } - result.putMap(Metadata.SERVICE_DATA, serviceData); - } else { - result.putNull(Metadata.SERVICE_DATA); - } - - if (advData.getServiceUUIDs() != null) { - WritableArray serviceUUIDs = Arguments.createArray(); - for (UUID serviceUUID : advData.getServiceUUIDs()) { - serviceUUIDs.pushString(UUIDConverter.fromUUID(serviceUUID)); - } - result.putArray(Metadata.SERVICE_UUIDS, serviceUUIDs); - } else { - result.putNull(Metadata.SERVICE_UUIDS); - } - - if (advData.getLocalName() != null) { - result.putString(Metadata.LOCAL_NAME, advData.getLocalName()); - } else { - result.putNull(Metadata.LOCAL_NAME); - } - - if (advData.getTxPowerLevel() != null) { - result.putInt(Metadata.TX_POWER_LEVEL, advData.getTxPowerLevel()); - } else { - result.putNull(Metadata.TX_POWER_LEVEL); - } - - if (advData.getSolicitedServiceUUIDs() != null) { - WritableArray solicitedServiceUUIDs = Arguments.createArray(); - for (UUID serviceUUID : advData.getSolicitedServiceUUIDs()) { - solicitedServiceUUIDs.pushString(UUIDConverter.fromUUID(serviceUUID)); - } - result.putArray(Metadata.SOLICITED_SERVICE_UUIDS, solicitedServiceUUIDs); - } else { - result.putNull(Metadata.SOLICITED_SERVICE_UUIDS); - } - - if (advData.getRawScanRecord() != null) { - result.putString(Metadata.RAW_SCAN_RECORD, Base64Converter.encode(advData.getRawScanRecord())); - } else { - result.putNull(Metadata.RAW_SCAN_RECORD); - } - - // Attributes which are not accessible on Android - result.putNull(Metadata.OVERFLOW_SERVICE_UUIDS); - return result; - } -} diff --git a/android/src/main/java/com/bleplx/converter/ServiceToJsObjectConverter.java b/android/src/main/java/com/bleplx/converter/ServiceToJsObjectConverter.java deleted file mode 100644 index 68853233e..000000000 --- a/android/src/main/java/com/bleplx/converter/ServiceToJsObjectConverter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.bleplx.converter; - -import com.bleplx.adapter.Service; -import com.bleplx.adapter.utils.UUIDConverter; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; - -public class ServiceToJsObjectConverter extends JSObjectConverter { - - private interface Metadata { - String ID = "id"; - String UUID = "uuid"; - String DEVICE_ID = "deviceID"; - String IS_PRIMARY = "isPrimary"; - } - - @Override - public WritableMap toJSObject(Service service) { - WritableMap result = Arguments.createMap(); - result.putInt(Metadata.ID, service.getId()); - result.putString(Metadata.UUID, UUIDConverter.fromUUID(service.getUuid())); - result.putString(Metadata.DEVICE_ID, service.getDeviceID()); - result.putBoolean(Metadata.IS_PRIMARY, service.isPrimary()); - return result; - } -} diff --git a/android/src/main/java/com/bleplx/utils/Base64Converter.java b/android/src/main/java/com/bleplx/utils/Base64Converter.java deleted file mode 100644 index 2f5b0d37d..000000000 --- a/android/src/main/java/com/bleplx/utils/Base64Converter.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.bleplx.utils; - -import android.util.Base64; - -public class Base64Converter { - public static String encode(byte[] bytes) { - return Base64.encodeToString(bytes, Base64.NO_WRAP); - } - - public static byte[] decode(String base64) { - return Base64.decode(base64, Base64.NO_WRAP); - } -} diff --git a/android/src/main/java/com/bleplx/utils/ErrorDefaults.java b/android/src/main/java/com/bleplx/utils/ErrorDefaults.java deleted file mode 100644 index b58ab8402..000000000 --- a/android/src/main/java/com/bleplx/utils/ErrorDefaults.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.bleplx.utils; - -public final class ErrorDefaults { - public static final String CODE = "UNKNOWN_ERROR"; - public static final String MESSAGE = ""; - - private ErrorDefaults() { - } -} diff --git a/android/src/main/java/com/bleplx/utils/ReadableArrayConverter.java b/android/src/main/java/com/bleplx/utils/ReadableArrayConverter.java deleted file mode 100644 index 2aa5987df..000000000 --- a/android/src/main/java/com/bleplx/utils/ReadableArrayConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.bleplx.utils; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.WritableArray; - -import java.util.List; -import java.util.UUID; - -public class ReadableArrayConverter { - public static String[] toStringArray(ReadableArray readableArray) { - String[] stringArray = new String[readableArray.size()]; - for (int i = 0; i < readableArray.size(); ++i) { - stringArray[i] = readableArray.getString(i); - } - return stringArray; - } - - public static ReadableArray toReadableArray(List uuids) { - WritableArray array = Arguments.createArray(); - - for (UUID uuid : uuids) { - array.pushString(uuid.toString()); - } - - return array; - } -} diff --git a/android/src/main/java/com/bleplx/utils/SafePromise.java b/android/src/main/java/com/bleplx/utils/SafePromise.java deleted file mode 100644 index 8d86f7314..000000000 --- a/android/src/main/java/com/bleplx/utils/SafePromise.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.bleplx.utils; - -import com.facebook.react.bridge.Promise; - -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.annotation.Nullable; - -public class SafePromise { - private Promise promise; - private AtomicBoolean isFinished = new AtomicBoolean(); - - public SafePromise(Promise promise) { - this.promise = promise; - } - - public void resolve(@Nullable Object value) { - if (isFinished.compareAndSet(false, true)) { - promise.resolve(value); - } - } - - public void reject(@Nullable String code, @Nullable String message) { - if (isFinished.compareAndSet(false, true)) { - String safeCode = code == null ? ErrorDefaults.CODE : code; - String safeMessage = message == null ? ErrorDefaults.MESSAGE : message; - promise.reject(safeCode, safeMessage); - } - } - - public void reject(@Nullable String code, Throwable e) { - if (isFinished.compareAndSet(false, true)) { - String safeCode = code == null ? ErrorDefaults.CODE : code; - promise.reject(safeCode, e); - } - } - - public void reject(@Nullable String code, @Nullable String message, Throwable e) { - if (isFinished.compareAndSet(false, true)) { - String safeCode = code == null ? ErrorDefaults.CODE : code; - String safeMessage = message == null ? ErrorDefaults.MESSAGE : message; - promise.reject(safeCode, safeMessage, e); - } - } - - @Deprecated - public void reject(@Nullable String message) { - if (isFinished.compareAndSet(false, true)) { - String safeMessage = message == null ? ErrorDefaults.MESSAGE : message; - promise.reject(ErrorDefaults.CODE, safeMessage); - } - } - - public void reject(Throwable reason) { - if (isFinished.compareAndSet(false, true)) { - promise.reject(reason); - } - } -} diff --git a/android/src/main/java/com/bleplx/utils/UUIDConverter.java b/android/src/main/java/com/bleplx/utils/UUIDConverter.java deleted file mode 100644 index d36ad63e4..000000000 --- a/android/src/main/java/com/bleplx/utils/UUIDConverter.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.bleplx.utils; - -import com.facebook.react.bridge.ReadableArray; - -import java.util.UUID; - -public class UUIDConverter { - - private static String baseUUIDPrefix = "0000"; - private static String baseUUIDSuffix = "-0000-1000-8000-00805F9B34FB"; - - public static UUID convert(String sUUID) { - if (sUUID == null) return null; - - if (sUUID.length() == 4) { - sUUID = baseUUIDPrefix + sUUID + baseUUIDSuffix; - } else if (sUUID.length() == 8) { - sUUID = sUUID + baseUUIDSuffix; - } - - try { - return UUID.fromString(sUUID); - } catch (Throwable e) { - return null; - } - } - - public static UUID[] convert(String... sUUIDs) { - UUID[] UUIDs = new UUID[sUUIDs.length]; - for (int i = 0; i < sUUIDs.length; i++) { - - if (sUUIDs[i] == null) return null; - - if (sUUIDs[i].length() == 4) { - sUUIDs[i] = baseUUIDPrefix + sUUIDs[i] + baseUUIDSuffix; - } else if (sUUIDs[i].length() == 8) { - sUUIDs[i] = sUUIDs[i] + baseUUIDSuffix; - } - - try { - UUIDs[i] = UUID.fromString(sUUIDs[i]); - } catch (Throwable e) { - return null; - } - } - return UUIDs; - } - - public static UUID[] convert(ReadableArray aUUIDs) { - UUID[] UUIDs = new UUID[aUUIDs.size()]; - for (int i = 0; i < aUUIDs.size(); i++) { - try { - String sUUID = aUUIDs.getString(i); - if (sUUID.length() == 4) { - sUUID = baseUUIDPrefix + sUUID + baseUUIDSuffix; - } else if (sUUID.length() == 8) { - sUUID = sUUID + baseUUIDSuffix; - } - UUIDs[i] = UUID.fromString(sUUID); - } catch (Throwable e) { - return null; - } - } - return UUIDs; - } - - public static String fromUUID(UUID uuid) { - return uuid.toString().toLowerCase(); - } -} diff --git a/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt new file mode 100644 index 000000000..35dd2f595 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt @@ -0,0 +1,305 @@ +package com.bleplx + +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattService +import android.content.Context +import android.os.Build +import android.util.Log +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.suspendCancellableCoroutine +import no.nordicsemi.android.ble.BleManager +import no.nordicsemi.android.ble.callback.DataReceivedCallback +import no.nordicsemi.android.ble.data.Data +import no.nordicsemi.android.ble.ktx.suspend +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * Per-device Nordic BleManager subclass. + * + * Caches discovered characteristics in a ConcurrentHashMap for fast lookup. + * Exposes suspend functions for GATT operations. + * + * Note: Nordic BLE Library does not support cancellation of enqueued GATT operations. + * When a transaction is cancelled from JS, the wrapping coroutine is cancelled but the + * underlying GATT operation continues to completion on the device. The response is discarded. + * This is a known limitation — BLE operations are atomic at the protocol level and cannot be + * interrupted once submitted to the Bluetooth stack. + */ +class BleManagerWrapper(context: Context) : BleManager(context) { + + companion object { + private const val TAG = "BleManagerWrapper" + private const val DEFAULT_MTU = 517 + } + + /** Service UUID -> (Characteristic UUID -> BluetoothGattCharacteristic) */ + private val characteristicCache = ConcurrentHashMap>() + + /** All discovered services */ + private val discoveredServices = ConcurrentHashMap() + + var deviceAddress: String = "" + private set + + var deviceName: String? = null + private set + + var currentMtu: Int = 23 + private set + + var lastRssi: Int = 0 + + // ---- BleManager overrides ---- + + override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { + // Cache all discovered services and characteristics + characteristicCache.clear() + discoveredServices.clear() + gatt.services?.forEach { service -> + val serviceUuid = service.uuid.toString() + discoveredServices[serviceUuid] = service + val charMap = ConcurrentHashMap() + service.characteristics?.forEach { char -> + charMap[char.uuid.toString()] = char + } + characteristicCache[serviceUuid] = charMap + } + // We support any device — the JS layer decides what services are required + return true + } + + override fun initialize() { + super.initialize() + // Request MTU 517 on API < 34 (Android 14+ auto-negotiates) + if (Build.VERSION.SDK_INT < 34) { + requestMtu(DEFAULT_MTU) + .with { _, mtu -> currentMtu = mtu } + .enqueue() + } + } + + override fun onServicesInvalidated() { + characteristicCache.clear() + discoveredServices.clear() + } + + // ---- Connection ---- + + /** + * Connect to a device with optional retry and timeout. + */ + suspend fun connectDevice( + device: BluetoothDevice, + autoConnect: Boolean = false, + retries: Int = 0, + retryDelay: Long = 1000, + timeoutMs: Long = 30_000 + ) { + deviceAddress = device.address + try { + deviceName = device.name + } catch (_: SecurityException) { + // Ignore — name is optional + } + + connect(device) + .useAutoConnect(autoConnect) + .retry(retries, retryDelay.toInt()) + .timeout(timeoutMs) + .suspend() + } + + suspend fun disconnectDevice() { + disconnect().suspend() + } + + // ---- Discovery ---- + + fun getDiscoveredServices(): List { + return discoveredServices.values.toList() + } + + fun getCharacteristicsForService(serviceUuid: String): List? { + return characteristicCache[serviceUuid]?.values?.toList() + } + + fun findCharacteristic(serviceUuid: String, characteristicUuid: String): BluetoothGattCharacteristic? { + return characteristicCache[serviceUuid]?.get(characteristicUuid) + } + + // ---- Read ---- + + suspend fun readChar(serviceUuid: String, characteristicUuid: String): ByteArray { + val char = findCharacteristic(serviceUuid, characteristicUuid) + ?: throw IllegalArgumentException("Characteristic $characteristicUuid not found in service $serviceUuid") + + return suspendCancellableCoroutine { cont -> + readCharacteristic(char) + .with { _, data -> cont.resume(data.value ?: ByteArray(0)) } + .fail { _, status -> + cont.resumeWithException( + GattException(status, "Read failed for $characteristicUuid") + ) + } + .enqueue() + } + } + + // ---- Write ---- + + suspend fun writeChar( + serviceUuid: String, + characteristicUuid: String, + value: ByteArray, + withResponse: Boolean + ): ByteArray { + val char = findCharacteristic(serviceUuid, characteristicUuid) + ?: throw IllegalArgumentException("Characteristic $characteristicUuid not found in service $serviceUuid") + + val writeType = if (withResponse) + BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT + else + BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE + + return suspendCancellableCoroutine { cont -> + writeCharacteristic(char, value, writeType) + .with { _, data -> cont.resume(data.value ?: value) } + .fail { _, status -> + cont.resumeWithException( + GattException(status, "Write failed for $characteristicUuid") + ) + } + .enqueue() + } + } + + // ---- Monitor (notification/indication) ---- + + fun monitorChar(serviceUuid: String, characteristicUuid: String): Flow = callbackFlow { + val char = findCharacteristic(serviceUuid, characteristicUuid) + ?: throw IllegalArgumentException("Characteristic $characteristicUuid not found in service $serviceUuid") + + val callback = DataReceivedCallback { _, data -> + trySend(data.value ?: ByteArray(0)) + } + + // Register notification callback before enabling + setNotificationCallback(char).with(callback) + + // Enable notifications or indications + val props = char.properties + val hasIndicate = props and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0 + val hasNotify = props and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0 + + if (hasIndicate) { + enableIndications(char) + .fail { _, status -> + close(GattException(status, "Enable indications failed for $characteristicUuid")) + } + .enqueue() + } else if (hasNotify) { + enableNotifications(char) + .fail { _, status -> + close(GattException(status, "Enable notifications failed for $characteristicUuid")) + } + .enqueue() + } else { + close(IllegalArgumentException("Characteristic $characteristicUuid supports neither notify nor indicate")) + return@callbackFlow + } + + awaitClose { + try { + if (hasIndicate) { + disableIndications(char).enqueue() + } else { + disableNotifications(char).enqueue() + } + } catch (e: Exception) { + Log.w(TAG, "Failed to disable notifications/indications on close", e) + } + } + } + + // ---- MTU ---- + + suspend fun requestMtuValue(mtu: Int): Int { + return suspendCancellableCoroutine { cont -> + requestMtu(mtu) + .with { _, negotiatedMtu -> + currentMtu = negotiatedMtu + cont.resume(negotiatedMtu) + } + .fail { _, status -> + cont.resumeWithException(GattException(status, "MTU request failed")) + } + .enqueue() + } + } + + // ---- RSSI ---- + + suspend fun readRemoteRssi(): Int { + return suspendCancellableCoroutine { cont -> + readRssi() + .with { _, rssi -> + lastRssi = rssi + cont.resume(rssi) + } + .fail { _, status -> + cont.resumeWithException(GattException(status, "RSSI read failed")) + } + .enqueue() + } + } + + // ---- Connection Priority ---- + + suspend fun requestConnectionPriorityValue(priority: Int) { + requestConnectionPriority(priority) + .suspend() + } + + // ---- PHY ---- + + suspend fun requestPhyValue(txPhy: Int, rxPhy: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setPreferredPhy(txPhy, rxPhy, BluetoothDevice.PHY_OPTION_NO_PREFERRED) + .suspend() + } + } + + suspend fun readPhyValue(): Pair { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return suspendCancellableCoroutine { cont -> + readPhy() + .with { _, txPhy, rxPhy -> + cont.resume(Pair(txPhy, rxPhy)) + } + .fail { _, status -> + cont.resumeWithException(GattException(status, "PHY read failed")) + } + .enqueue() + } + } + return Pair(1, 1) // PHY_LE_1M default + } + + // ---- Helpers ---- + + /** + * Exception carrying a GATT status code for ErrorConverter. + */ + class GattException(val status: Int, message: String) : Exception(message) + + override fun log(priority: Int, message: String) { + Log.println(priority, TAG, message) + } +} diff --git a/android/src/main/kotlin/com/bleplx/BlePlxModule.kt b/android/src/main/kotlin/com/bleplx/BlePlxModule.kt new file mode 100644 index 000000000..1b3f9da33 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/BlePlxModule.kt @@ -0,0 +1,788 @@ +package com.bleplx + +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.os.ParcelUuid +import android.util.Base64 +import android.util.Log +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableMap +import com.facebook.react.module.annotations.ReactModule +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import no.nordicsemi.android.support.v18.scanner.ScanResult +import java.util.concurrent.ConcurrentHashMap + +/** + * Kotlin TurboModule for react-native-ble-plx v4. + * + * Extends the Codegen-generated NativeBlePlxSpec base class (generated at build time). + * Uses Nordic Android-BLE-Library for GATT operations and Scanner Compat for scanning. + */ +@ReactModule(name = BlePlxModule.NAME) +class BlePlxModule(private val reactContext: ReactApplicationContext) : NativeBlePlxSpec(reactContext) { + + companion object { + const val NAME = "NativeBlePlx" + private const val TAG = "BlePlxModule" + } + + private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + private val connections = ConcurrentHashMap() + private val monitorJobs = ConcurrentHashMap() + private val pendingTransactions = ConcurrentHashMap() + private var scanManager: ScanManager? = null + private var bluetoothStateReceiver: BroadcastReceiver? = null + private var isClientCreated = false + + override fun getName(): String = NAME + + // ---- Lifecycle ---- + + override fun createClient(restoreStateIdentifier: String?, promise: Promise) { + if (isClientCreated) { + promise.resolve(null) + return + } + + scanManager = ScanManager(reactContext) + registerBluetoothStateReceiver() + isClientCreated = true + + // Emit initial state + emitOnStateChange(EventSerializer.serializeStateChangeEvent(getCurrentBluetoothState())) + + promise.resolve(null) + } + + override fun destroyClient(promise: Promise) { + if (!guardClient("destroyClient", promise)) return + + // Cancel all monitoring jobs + monitorJobs.values.forEach { it.cancel() } + monitorJobs.clear() + + // Cancel pending transactions + pendingTransactions.values.forEach { it.cancel() } + pendingTransactions.clear() + + // Stop scanning + scanManager?.stopScan() + scanManager = null + + // Disconnect all devices + connections.values.forEach { wrapper -> + try { + wrapper.close() + } catch (e: Exception) { + Log.w(TAG, "Error closing connection: ${e.message}") + } + } + connections.clear() + + unregisterBluetoothStateReceiver() + isClientCreated = false + + promise.resolve(null) + } + + override fun invalidate() { + scope.cancel() + scanManager?.stopScan() + connections.values.forEach { it.close() } + connections.clear() + monitorJobs.values.forEach { it.cancel() } + monitorJobs.clear() + pendingTransactions.values.forEach { it.cancel() } + pendingTransactions.clear() + unregisterBluetoothStateReceiver() + isClientCreated = false + super.invalidate() + } + + // ---- State ---- + + override fun state(promise: Promise) { + promise.resolve(getCurrentBluetoothState()) + } + + // ---- Scanning ---- + + override fun startDeviceScan(uuids: ReadableArray?, options: ReadableMap?) { + if (!isClientCreated) { + emitOnError(ErrorConverter.toWritableMap( + ErrorConverter.BleErrorInfo( + code = ErrorConverter.BLUETOOTH_MANAGER_DESTROYED, + message = "Client not created", + isRetryable = false + ) + )) + return + } + + val uuidList = uuids?.let { array -> + (0 until array.size()).mapNotNull { array.getString(it) } + } + + val scanMode = options?.let { + if (it.hasKey("scanMode")) it.getInt("scanMode") else 0 + } ?: 0 + + val callbackType = options?.let { + if (it.hasKey("callbackType")) it.getInt("callbackType") else 1 + } ?: 1 + + val legacyScan = options?.let { + if (it.hasKey("legacyScan")) it.getBoolean("legacyScan") else true + } ?: true + + val error = scanManager?.startScan(uuidList, scanMode, callbackType, legacyScan, object : ScanManager.ScanListener { + override fun onScanResult(result: ScanResult) { + val device = result.device + val scanRecord = result.scanRecord + val serviceUuids = scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList() + val manufacturerData = scanRecord?.let { record -> + // Combine all manufacturer specific data + val data = record.manufacturerSpecificData + if (data != null && data.size() > 0) { + val key = data.keyAt(0) + data.get(key) + } else null + } + + var deviceName: String? = null + try { + deviceName = scanRecord?.deviceName ?: device.name + } catch (_: SecurityException) { + // name is optional + } + + emitOnScanResult(EventSerializer.serializeScanResult( + deviceAddress = device.address, + deviceName = deviceName, + rssi = result.rssi, + serviceUuids = serviceUuids, + manufacturerData = manufacturerData + )) + } + + override fun onScanFailed(errorCode: Int) { + emitOnError(ErrorConverter.toWritableMap(ErrorConverter.fromScanError(errorCode))) + } + }) + + if (error != null) { + emitOnError(ErrorConverter.toWritableMap(error)) + } + } + + override fun stopDeviceScan(promise: Promise) { + scanManager?.stopScan() + promise.resolve(null) + } + + // ---- Connection ---- + + override fun connectToDevice(deviceId: String, options: ReadableMap?, promise: Promise) { + if (!guardClient("connectToDevice", promise)) return + + scope.launch { + try { + val adapter = getBluetoothAdapter() + if (adapter == null) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.BLUETOOTH_UNSUPPORTED, + message = "Bluetooth not available", + isRetryable = false, + deviceId = deviceId + )) + return@launch + } + + if (!PermissionHelper.hasPermissions(reactContext, PermissionHelper.PermissionGroup.CONNECT)) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.CONNECT_PERMISSION_DENIED, + message = "Missing BLUETOOTH_CONNECT permission", + isRetryable = false, + deviceId = deviceId + )) + return@launch + } + + // Parse options + val autoConnect = options?.let { if (it.hasKey("autoConnect")) it.getBoolean("autoConnect") else false } ?: false + val timeout = options?.let { if (it.hasKey("timeout")) it.getInt("timeout").toLong() else 30_000L } ?: 30_000L + val retries = options?.let { if (it.hasKey("retries")) it.getInt("retries") else 0 } ?: 0 + val retryDelay = options?.let { if (it.hasKey("retryDelay")) it.getInt("retryDelay").toLong() else 1000L } ?: 1000L + val requestMtu = options?.let { if (it.hasKey("requestMtu")) it.getInt("requestMtu") else 0 } ?: 0 + + // Reuse or create BleManagerWrapper + val wrapper = connections.getOrPut(deviceId) { BleManagerWrapper(reactContext) } + + // Set up disconnect listener + wrapper.setConnectionObserver(object : no.nordicsemi.android.ble.observer.ConnectionObserver { + override fun onDeviceConnecting(device: BluetoothDevice) { + emitOnConnectionStateChange(EventSerializer.serializeConnectionStateEvent(deviceId, "connecting")) + } + + override fun onDeviceConnected(device: BluetoothDevice) { + emitOnConnectionStateChange(EventSerializer.serializeConnectionStateEvent(deviceId, "connected")) + } + + override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) { + val error = ErrorConverter.fromGattStatus(reason, deviceId, "connect") + emitOnConnectionStateChange(EventSerializer.serializeConnectionStateEvent( + deviceId, "disconnected", error.code, error.message + )) + connections.remove(deviceId) + } + + override fun onDeviceReady(device: BluetoothDevice) { + // Already emitted connected + } + + override fun onDeviceDisconnecting(device: BluetoothDevice) { + emitOnConnectionStateChange(EventSerializer.serializeConnectionStateEvent(deviceId, "disconnecting")) + } + + override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) { + // Cancel any monitoring for this device + monitorJobs.keys.filter { it.startsWith(deviceId) }.forEach { key -> + monitorJobs.remove(key)?.cancel() + } + emitOnConnectionStateChange(EventSerializer.serializeConnectionStateEvent(deviceId, "disconnected")) + connections.remove(deviceId) + } + }) + + val device = adapter.getRemoteDevice(deviceId) + wrapper.connectDevice(device, autoConnect, retries, retryDelay, timeout) + + // Request custom MTU if specified + if (requestMtu > 0) { + wrapper.requestMtuValue(requestMtu) + } + + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper.deviceName, + mtu = wrapper.currentMtu + )) + } catch (e: SecurityException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "connect"), e) + } catch (e: BleManagerWrapper.GattException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromGattStatus(e.status, deviceId, "connect"), e) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "connect"), e) + } + } + } + + override fun cancelDeviceConnection(deviceId: String, promise: Promise) { + if (!guardClient("cancelDeviceConnection", promise)) return + + scope.launch { + try { + val wrapper = connections[deviceId] + if (wrapper != null) { + wrapper.disconnectDevice() + } + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper?.deviceName + )) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "disconnect"), e) + } + } + } + + override fun isDeviceConnected(deviceId: String, promise: Promise) { + if (!guardClient("isDeviceConnected", promise)) return + val wrapper = connections[deviceId] + promise.resolve(wrapper?.isConnected ?: false) + } + + // ---- Discovery ---- + + override fun discoverAllServicesAndCharacteristics(deviceId: String, transactionId: String?, promise: Promise) { + if (!guardClient("discoverAllServicesAndCharacteristics", promise)) return + + val wrapper = connections[deviceId] + if (wrapper == null) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + return + } + + // Nordic BLE Library auto-discovers services on connect. + // Services are already cached in the wrapper. + val serviceUuids = wrapper.getDiscoveredServices().map { it.uuid.toString() } + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper.deviceName, + mtu = wrapper.currentMtu, + serviceUuids = serviceUuids + )) + } + + // ---- Read/Write ---- + + override fun readCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + transactionId: String?, + promise: Promise + ) { + if (!guardClient("readCharacteristic", promise)) return + + val job = scope.launch { + try { + val wrapper = getConnectedWrapper(deviceId) + ?: return@launch ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + + val value = wrapper.readChar(serviceUuid, characteristicUuid) + val char = wrapper.findCharacteristic(serviceUuid, characteristicUuid) + + promise.resolve(EventSerializer.serializeCharacteristicSimple( + deviceId = deviceId, + serviceUuid = serviceUuid, + characteristicUuid = characteristicUuid, + value = value, + properties = char?.properties ?: 0 + )) + } catch (e: BleManagerWrapper.GattException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromGattStatus(e.status, deviceId, "readCharacteristic"), e) + } catch (e: CancellationException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.OPERATION_CANCELLED, + message = "Read cancelled", + isRetryable = false, + deviceId = deviceId + )) + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "readCharacteristic"), e) + } + } + + transactionId?.let { pendingTransactions[it] = job } + } + + override fun writeCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + value: String, + withResponse: Boolean, + transactionId: String?, + promise: Promise + ) { + if (!guardClient("writeCharacteristic", promise)) return + + val job = scope.launch { + try { + val wrapper = getConnectedWrapper(deviceId) + ?: return@launch ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + + val bytes = Base64.decode(value, Base64.DEFAULT) + val written = wrapper.writeChar(serviceUuid, characteristicUuid, bytes, withResponse) + val char = wrapper.findCharacteristic(serviceUuid, characteristicUuid) + + promise.resolve(EventSerializer.serializeCharacteristicSimple( + deviceId = deviceId, + serviceUuid = serviceUuid, + characteristicUuid = characteristicUuid, + value = written, + properties = char?.properties ?: 0 + )) + } catch (e: BleManagerWrapper.GattException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromGattStatus(e.status, deviceId, "writeCharacteristic"), e) + } catch (e: CancellationException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.OPERATION_CANCELLED, + message = "Write cancelled", + isRetryable = false, + deviceId = deviceId + )) + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "writeCharacteristic"), e) + } + } + + transactionId?.let { pendingTransactions[it] = job } + } + + // ---- Monitor ---- + + override fun monitorCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + subscriptionType: String?, + transactionId: String? + ) { + if (!isClientCreated) { + emitOnError(ErrorConverter.toWritableMap(ErrorConverter.BleErrorInfo( + code = ErrorConverter.BLUETOOTH_MANAGER_DESTROYED, + message = "Client not created", + isRetryable = false, + deviceId = deviceId + ))) + return + } + + val wrapper = connections[deviceId] + if (wrapper == null) { + emitOnError(ErrorConverter.toWritableMap(ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + ))) + return + } + + val monitorKey = "$deviceId|$serviceUuid|$characteristicUuid" + + // Cancel existing monitor for this characteristic + monitorJobs.remove(monitorKey)?.cancel() + + val job = scope.launch { + try { + wrapper.monitorChar(serviceUuid, characteristicUuid).collect { value -> + emitOnCharacteristicValueUpdate(EventSerializer.serializeCharacteristicValueEvent( + deviceId = deviceId, + serviceUuid = serviceUuid, + characteristicUuid = characteristicUuid, + value = value, + transactionId = transactionId + )) + } + } catch (e: CancellationException) { + // Normal cancellation, do nothing + } catch (e: BleManagerWrapper.GattException) { + emitOnError(ErrorConverter.toWritableMap(ErrorConverter.fromGattStatus(e.status, deviceId, "monitorCharacteristic"))) + } catch (e: Exception) { + emitOnError(ErrorConverter.toWritableMap(ErrorConverter.fromException(e, deviceId, "monitorCharacteristic"))) + } finally { + monitorJobs.remove(monitorKey) + } + } + + monitorJobs[monitorKey] = job + transactionId?.let { pendingTransactions[it] = job } + } + + // ---- MTU ---- + + override fun getMtu(deviceId: String, promise: Promise) { + if (!guardClient("getMtu", promise)) return + val wrapper = connections[deviceId] + if (wrapper == null) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + return + } + promise.resolve(wrapper.currentMtu.toDouble()) + } + + override fun requestMtu(deviceId: String, mtu: Double, transactionId: String?, promise: Promise) { + if (!guardClient("requestMtu", promise)) return + + val job = scope.launch { + try { + val wrapper = getConnectedWrapper(deviceId) + ?: return@launch ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + + wrapper.requestMtuValue(mtu.toInt()) + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper.deviceName, + mtu = wrapper.currentMtu + )) + } catch (e: BleManagerWrapper.GattException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromGattStatus(e.status, deviceId, "requestMtu"), e) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "requestMtu"), e) + } + } + + transactionId?.let { pendingTransactions[it] = job } + } + + // ---- PHY ---- + + override fun requestPhy(deviceId: String, txPhy: Double, rxPhy: Double, promise: Promise) { + if (!guardClient("requestPhy", promise)) return + + scope.launch { + try { + val wrapper = getConnectedWrapper(deviceId) + ?: return@launch ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + + wrapper.requestPhyValue(txPhy.toInt(), rxPhy.toInt()) + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper.deviceName, + mtu = wrapper.currentMtu + )) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "requestPhy"), e) + } + } + } + + override fun readPhy(deviceId: String, promise: Promise) { + if (!guardClient("readPhy", promise)) return + + scope.launch { + try { + val wrapper = getConnectedWrapper(deviceId) + ?: return@launch ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + + wrapper.readPhyValue() + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper.deviceName, + mtu = wrapper.currentMtu + )) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "readPhy"), e) + } + } + } + + // ---- Connection Priority ---- + + override fun requestConnectionPriority(deviceId: String, priority: Double, promise: Promise) { + if (!guardClient("requestConnectionPriority", promise)) return + + scope.launch { + try { + val wrapper = getConnectedWrapper(deviceId) + ?: return@launch ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_NOT_CONNECTED, + message = "Device $deviceId is not connected", + isRetryable = false, + deviceId = deviceId + )) + + wrapper.requestConnectionPriorityValue(priority.toInt()) + promise.resolve(EventSerializer.serializeDeviceInfo( + deviceAddress = deviceId, + deviceName = wrapper.deviceName, + mtu = wrapper.currentMtu + )) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, deviceId, "requestConnectionPriority"), e) + } + } + } + + // ---- L2CAP (stub — not yet implemented in Nordic BLE Library) ---- + + override fun openL2CAPChannel(deviceId: String, psm: Double, promise: Promise) { + promise.reject( + ErrorConverter.OPERATION_START_FAILED.toString(), + "L2CAP channels are not yet supported on Android" + ) + } + + override fun writeL2CAPChannel(channelId: Double, data: String, promise: Promise) { + promise.reject( + ErrorConverter.OPERATION_START_FAILED.toString(), + "L2CAP channels are not yet supported on Android" + ) + } + + override fun closeL2CAPChannel(channelId: Double, promise: Promise) { + promise.reject( + ErrorConverter.OPERATION_START_FAILED.toString(), + "L2CAP channels are not yet supported on Android" + ) + } + + // ---- Bonding ---- + + override fun getBondedDevices(promise: Promise) { + if (!guardClient("getBondedDevices", promise)) return + + try { + val adapter = getBluetoothAdapter() + if (adapter == null) { + promise.resolve(Arguments.createArray()) + return + } + + val bondedDevices = adapter.bondedDevices + val result = Arguments.createArray() + bondedDevices?.forEach { device -> + var name: String? = null + try { + name = device.name + } catch (_: SecurityException) {} + + result.pushMap(EventSerializer.serializeDeviceInfo( + deviceAddress = device.address, + deviceName = name + )) + } + promise.resolve(result) + } catch (e: SecurityException) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.fromException(e, operation = "getBondedDevices"), e) + } + } + + // ---- Authorization ---- + + override fun getAuthorizationStatus(promise: Promise) { + val hasScan = PermissionHelper.hasPermissions(reactContext, PermissionHelper.PermissionGroup.SCAN) + val hasConnect = PermissionHelper.hasPermissions(reactContext, PermissionHelper.PermissionGroup.CONNECT) + + val status = when { + hasScan && hasConnect -> "granted" + !PermissionHelper.isBluetoothSupported(reactContext) -> "unsupported" + else -> "denied" + } + promise.resolve(status) + } + + // ---- Cancellation ---- + + override fun cancelTransaction(transactionId: String, promise: Promise) { + pendingTransactions.remove(transactionId)?.cancel() + promise.resolve(null) + } + + // ---- Internal helpers ---- + + private fun guardClient(methodName: String, promise: Promise): Boolean { + if (!isClientCreated) { + ErrorConverter.rejectPromise(promise::reject, ErrorConverter.BleErrorInfo( + code = ErrorConverter.BLUETOOTH_MANAGER_DESTROYED, + message = "BleManager cannot call $methodName because client has been destroyed", + isRetryable = false + )) + return false + } + return true + } + + private fun getConnectedWrapper(deviceId: String): BleManagerWrapper? { + val wrapper = connections[deviceId] + return if (wrapper?.isConnected == true) wrapper else null + } + + private fun getBluetoothAdapter(): BluetoothAdapter? { + val manager = reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager + return manager?.adapter + } + + private fun getCurrentBluetoothState(): String { + if (!PermissionHelper.isBluetoothSupported(reactContext)) return "Unsupported" + val adapter = getBluetoothAdapter() ?: return "Unsupported" + + return try { + when (adapter.state) { + BluetoothAdapter.STATE_ON -> "PoweredOn" + BluetoothAdapter.STATE_OFF -> "PoweredOff" + BluetoothAdapter.STATE_TURNING_ON, + BluetoothAdapter.STATE_TURNING_OFF -> "Resetting" + else -> "Unknown" + } + } catch (_: SecurityException) { + "Unauthorized" + } + } + + private fun registerBluetoothStateReceiver() { + if (bluetoothStateReceiver != null) return + + bluetoothStateReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) { + emitOnStateChange(EventSerializer.serializeStateChangeEvent(getCurrentBluetoothState())) + } + } + } + + val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + reactContext.registerReceiver(bluetoothStateReceiver, filter, Context.RECEIVER_EXPORTED) + } else { + reactContext.registerReceiver(bluetoothStateReceiver, filter) + } + } + + private fun unregisterBluetoothStateReceiver() { + bluetoothStateReceiver?.let { + try { + reactContext.unregisterReceiver(it) + } catch (_: Exception) {} + bluetoothStateReceiver = null + } + } +} diff --git a/android/src/main/kotlin/com/bleplx/BlePlxPackage.kt b/android/src/main/kotlin/com/bleplx/BlePlxPackage.kt new file mode 100644 index 000000000..56eb8dfeb --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/BlePlxPackage.kt @@ -0,0 +1,35 @@ +package com.bleplx + +import com.facebook.react.BaseReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider + +/** + * TurboModule ReactPackage registration for BlePlx. + */ +class BlePlxPackage : BaseReactPackage() { + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return when (name) { + BlePlxModule.NAME -> BlePlxModule(reactContext) + else -> null + } + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + mapOf( + BlePlxModule.NAME to ReactModuleInfo( + BlePlxModule.NAME, + BlePlxModule.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // isCxxModule + true // isTurboModule + ) + ) + } + } +} diff --git a/android/src/main/kotlin/com/bleplx/ErrorConverter.kt b/android/src/main/kotlin/com/bleplx/ErrorConverter.kt new file mode 100644 index 000000000..638dd24d2 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/ErrorConverter.kt @@ -0,0 +1,237 @@ +package com.bleplx + +import android.bluetooth.BluetoothGatt +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap + +/** + * Converts Android BLE errors into unified BleErrorCode values + * that map to the JS-side BleErrorInfo type. + */ +object ErrorConverter { + + // ---- BleErrorCode constants (mirrors NativeBlePlx.ts BleErrorInfo.code) ---- + const val UNKNOWN_ERROR = 0 + const val BLUETOOTH_MANAGER_DESTROYED = 1 + const val OPERATION_CANCELLED = 2 + const val OPERATION_TIMED_OUT = 3 + const val OPERATION_START_FAILED = 4 + const val INVALID_IDENTIFIERS = 5 + + const val BLUETOOTH_UNSUPPORTED = 100 + const val BLUETOOTH_UNAUTHORIZED = 101 + const val BLUETOOTH_POWERED_OFF = 102 + const val BLUETOOTH_IN_UNKNOWN_STATE = 103 + const val BLUETOOTH_RESETTING = 104 + + const val DEVICE_CONNECTION_FAILED = 200 + const val DEVICE_DISCONNECTED = 201 + const val DEVICE_RSSI_READ_FAILED = 202 + const val DEVICE_ALREADY_CONNECTED = 203 + const val DEVICE_NOT_FOUND = 204 + const val DEVICE_NOT_CONNECTED = 205 + const val DEVICE_MTU_CHANGE_FAILED = 206 + + const val SERVICES_DISCOVERY_FAILED = 300 + const val SERVICE_NOT_FOUND = 302 + const val SERVICES_NOT_DISCOVERED = 303 + + const val CHARACTERISTIC_WRITE_FAILED = 401 + const val CHARACTERISTIC_READ_FAILED = 402 + const val CHARACTERISTIC_NOTIFY_CHANGE_FAILED = 403 + const val CHARACTERISTIC_NOT_FOUND = 404 + const val CHARACTERISTICS_NOT_DISCOVERED = 405 + + const val DESCRIPTOR_WRITE_FAILED = 501 + const val DESCRIPTOR_READ_FAILED = 502 + const val DESCRIPTOR_NOT_FOUND = 503 + + const val SCAN_START_FAILED = 600 + const val LOCATION_SERVICES_DISABLED = 601 + const val SCAN_THROTTLED = 602 + const val CONNECT_PERMISSION_DENIED = 603 + + // ---- GATT status → error code mapping ---- + + data class BleErrorInfo( + val code: Int, + val message: String, + val isRetryable: Boolean, + val deviceId: String? = null, + val serviceUuid: String? = null, + val characteristicUuid: String? = null, + val operation: String? = null, + val nativeDomain: String? = null, + val nativeCode: Int? = null, + val gattStatus: Int? = null + ) + + fun fromGattStatus( + status: Int, + deviceId: String? = null, + operation: String? = null + ): BleErrorInfo { + return when (status) { + BluetoothGatt.GATT_SUCCESS -> BleErrorInfo( + code = UNKNOWN_ERROR, + message = "Operation succeeded but was treated as error", + isRetryable = false, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_CONN_TERMINATE_PEER_USER (0x13 = 19) + 0x13 -> BleErrorInfo( + code = DEVICE_DISCONNECTED, + message = "Device disconnected by peer", + isRetryable = false, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_CONN_TIMEOUT (0x08 = 8) + 0x08 -> BleErrorInfo( + code = OPERATION_TIMED_OUT, + message = "GATT connection timeout", + isRetryable = true, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_ERROR (0x85 = 133) - the infamous Android error + 0x85 -> BleErrorInfo( + code = DEVICE_CONNECTION_FAILED, + message = "GATT error 133 — connection failed", + isRetryable = true, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_CONN_TERMINATE_LOCAL_HOST (0x16 = 22) + 0x16 -> BleErrorInfo( + code = DEVICE_DISCONNECTED, + message = "Connection terminated by local host (GATT 0x16)", + isRetryable = true, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_CONN_LMP_TIMEOUT (0x22 = 34) + 0x22 -> BleErrorInfo( + code = DEVICE_CONNECTION_FAILED, + message = "Connection failed - LMP timeout (GATT 0x22)", + isRetryable = true, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_CONN_FAIL_ESTABLISH (0x3E = 62) + 0x3E -> BleErrorInfo( + code = DEVICE_CONNECTION_FAILED, + message = "Connection failed to establish (GATT 0x3E)", + isRetryable = true, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + // GATT_INSUFFICIENT_AUTHENTICATION + 0x05 -> BleErrorInfo( + code = BLUETOOTH_UNAUTHORIZED, + message = "Insufficient authentication", + isRetryable = false, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + else -> BleErrorInfo( + code = UNKNOWN_ERROR, + message = "GATT error $status", + isRetryable = false, + deviceId = deviceId, + operation = operation, + gattStatus = status + ) + } + } + + fun fromException( + exception: Throwable, + deviceId: String? = null, + operation: String? = null + ): BleErrorInfo { + return when (exception) { + is SecurityException -> BleErrorInfo( + code = CONNECT_PERMISSION_DENIED, + message = "Missing BLE permission: ${exception.message}", + isRetryable = false, + deviceId = deviceId, + operation = operation, + nativeDomain = "SecurityException" + ) + is IllegalStateException -> BleErrorInfo( + code = BLUETOOTH_MANAGER_DESTROYED, + message = exception.message ?: "Illegal state", + isRetryable = false, + deviceId = deviceId, + operation = operation + ) + else -> BleErrorInfo( + code = UNKNOWN_ERROR, + message = exception.message ?: "Unknown error", + isRetryable = false, + deviceId = deviceId, + operation = operation, + nativeDomain = exception.javaClass.simpleName + ) + } + } + + fun fromScanError(errorCode: Int): BleErrorInfo { + val message = when (errorCode) { + 1 -> "Scan already started" + 2 -> "Application registration failed" + 3 -> "Internal error" + 4 -> "Feature unsupported" + 5 -> "Out of hardware resources" + 6 -> "Scanning too frequently" + else -> "Scan error $errorCode" + } + return BleErrorInfo( + code = SCAN_START_FAILED, + message = message, + isRetryable = errorCode == 6, // scanning too frequently is retryable + nativeCode = errorCode + ) + } + + fun scanThrottled(): BleErrorInfo = BleErrorInfo( + code = SCAN_THROTTLED, + message = "Scan throttled: 5 starts in 30 seconds limit reached", + isRetryable = true + ) + + fun toWritableMap(info: BleErrorInfo): WritableMap { + val map = Arguments.createMap() + map.putInt("code", info.code) + map.putString("message", info.message) + map.putBoolean("isRetryable", info.isRetryable) + if (info.deviceId != null) map.putString("deviceId", info.deviceId) else map.putNull("deviceId") + if (info.serviceUuid != null) map.putString("serviceUuid", info.serviceUuid) else map.putNull("serviceUuid") + if (info.characteristicUuid != null) map.putString("characteristicUuid", info.characteristicUuid) else map.putNull("characteristicUuid") + if (info.operation != null) map.putString("operation", info.operation) else map.putNull("operation") + map.putString("platform", "android") + if (info.nativeDomain != null) map.putString("nativeDomain", info.nativeDomain) else map.putNull("nativeDomain") + if (info.nativeCode != null) map.putInt("nativeCode", info.nativeCode) else map.putNull("nativeCode") + if (info.gattStatus != null) map.putInt("gattStatus", info.gattStatus) else map.putNull("gattStatus") + map.putNull("attErrorCode") // Android doesn't use ATT error codes + return map + } + + fun rejectPromise( + reject: (String, String, Throwable?) -> Unit, + info: BleErrorInfo, + cause: Throwable? = null + ) { + reject(info.code.toString(), info.message, cause) + } +} diff --git a/android/src/main/kotlin/com/bleplx/EventSerializer.kt b/android/src/main/kotlin/com/bleplx/EventSerializer.kt new file mode 100644 index 000000000..5b9017923 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/EventSerializer.kt @@ -0,0 +1,168 @@ +package com.bleplx + +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattService +import android.util.Base64 +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap + +/** + * Serializes native BLE objects into the Codegen event types + * defined in NativeBlePlx.ts (ScanResult, DeviceInfo, etc.) + */ +object EventSerializer { + + // ---- ScanResult ---- + + fun serializeScanResult( + deviceAddress: String, + deviceName: String?, + rssi: Int, + serviceUuids: List, + manufacturerData: ByteArray? + ): WritableMap { + val map = Arguments.createMap() + map.putString("id", deviceAddress) + if (deviceName != null) map.putString("name", deviceName) else map.putNull("name") + map.putInt("rssi", rssi) + val uuidsArray = Arguments.createArray() + serviceUuids.forEach { uuidsArray.pushString(it) } + map.putArray("serviceUuids", uuidsArray) + if (manufacturerData != null) { + map.putString("manufacturerData", Base64.encodeToString(manufacturerData, Base64.NO_WRAP)) + } else { + map.putNull("manufacturerData") + } + return map + } + + // ---- DeviceInfo ---- + + fun serializeDeviceInfo( + deviceAddress: String, + deviceName: String?, + rssi: Int = 0, + mtu: Int = 23, + isConnectable: Boolean? = null, + serviceUuids: List = emptyList(), + manufacturerData: ByteArray? = null + ): WritableMap { + val map = Arguments.createMap() + map.putString("id", deviceAddress) + if (deviceName != null) map.putString("name", deviceName) else map.putNull("name") + map.putInt("rssi", rssi) + map.putInt("mtu", mtu) + if (isConnectable != null) map.putBoolean("isConnectable", isConnectable) else map.putNull("isConnectable") + val uuidsArray = Arguments.createArray() + serviceUuids.forEach { uuidsArray.pushString(it) } + map.putArray("serviceUuids", uuidsArray) + if (manufacturerData != null) { + map.putString("manufacturerData", Base64.encodeToString(manufacturerData, Base64.NO_WRAP)) + } else { + map.putNull("manufacturerData") + } + return map + } + + // ---- CharacteristicInfo ---- + + fun serializeCharacteristic( + deviceId: String, + serviceUuid: String, + characteristic: BluetoothGattCharacteristic, + value: ByteArray? = null + ): WritableMap { + val map = Arguments.createMap() + map.putString("deviceId", deviceId) + map.putString("serviceUuid", serviceUuid) + map.putString("uuid", characteristic.uuid.toString()) + if (value != null) { + map.putString("value", Base64.encodeToString(value, Base64.NO_WRAP)) + } else { + map.putNull("value") + } + val props = characteristic.properties + map.putBoolean("isNotifying", false) // Will be updated by monitor logic + map.putBoolean("isIndicatable", props and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) + map.putBoolean("isReadable", props and BluetoothGattCharacteristic.PROPERTY_READ != 0) + map.putBoolean("isWritableWithResponse", props and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) + map.putBoolean("isWritableWithoutResponse", props and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) + return map + } + + fun serializeCharacteristicSimple( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + value: ByteArray?, + isNotifying: Boolean = false, + properties: Int = 0 + ): WritableMap { + val map = Arguments.createMap() + map.putString("deviceId", deviceId) + map.putString("serviceUuid", serviceUuid) + map.putString("uuid", characteristicUuid) + if (value != null) { + map.putString("value", Base64.encodeToString(value, Base64.NO_WRAP)) + } else { + map.putNull("value") + } + map.putBoolean("isNotifying", isNotifying) + map.putBoolean("isIndicatable", properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) + map.putBoolean("isReadable", properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) + map.putBoolean("isWritableWithResponse", properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) + map.putBoolean("isWritableWithoutResponse", properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) + return map + } + + // ---- ConnectionStateEvent ---- + + fun serializeConnectionStateEvent( + deviceId: String, + state: String, + errorCode: Int? = null, + errorMessage: String? = null + ): WritableMap { + val map = Arguments.createMap() + map.putString("deviceId", deviceId) + map.putString("state", state) + if (errorCode != null) map.putInt("errorCode", errorCode) else map.putNull("errorCode") + if (errorMessage != null) map.putString("errorMessage", errorMessage) else map.putNull("errorMessage") + return map + } + + // ---- CharacteristicValueEvent ---- + + fun serializeCharacteristicValueEvent( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + value: ByteArray, + transactionId: String? = null + ): WritableMap { + val map = Arguments.createMap() + map.putString("deviceId", deviceId) + map.putString("serviceUuid", serviceUuid) + map.putString("characteristicUuid", characteristicUuid) + map.putString("value", Base64.encodeToString(value, Base64.NO_WRAP)) + if (transactionId != null) map.putString("transactionId", transactionId) else map.putNull("transactionId") + return map + } + + // ---- StateChangeEvent ---- + + fun serializeStateChangeEvent(state: String): WritableMap { + val map = Arguments.createMap() + map.putString("state", state) + return map + } + + // ---- RestoreStateEvent ---- + + fun serializeRestoreStateEvent(devices: WritableArray): WritableMap { + val map = Arguments.createMap() + map.putArray("devices", devices) + return map + } +} diff --git a/android/src/main/kotlin/com/bleplx/PermissionHelper.kt b/android/src/main/kotlin/com/bleplx/PermissionHelper.kt new file mode 100644 index 000000000..45e650e76 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/PermissionHelper.kt @@ -0,0 +1,73 @@ +package com.bleplx + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.ContextCompat + +/** + * Runtime permission checks for BLE operations on Android. + * + * API < 31: BLUETOOTH + ACCESS_FINE_LOCATION + * API 31+: BLUETOOTH_SCAN + BLUETOOTH_CONNECT + */ +object PermissionHelper { + + enum class PermissionGroup { + SCAN, + CONNECT + } + + /** + * Returns the list of missing permissions for the requested group. + * Empty list means all permissions are granted. + */ + fun getMissingPermissions(context: Context, group: PermissionGroup): List { + val required = getRequiredPermissions(group) + return required.filter { + ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED + } + } + + fun hasPermissions(context: Context, group: PermissionGroup): Boolean { + return getMissingPermissions(context, group).isEmpty() + } + + fun getRequiredPermissions(group: PermissionGroup): List { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // API 31+ (Android 12+) + when (group) { + PermissionGroup.SCAN -> listOf(Manifest.permission.BLUETOOTH_SCAN) + PermissionGroup.CONNECT -> listOf(Manifest.permission.BLUETOOTH_CONNECT) + } + } else { + // API < 31 + when (group) { + PermissionGroup.SCAN -> listOf( + Manifest.permission.BLUETOOTH, + Manifest.permission.ACCESS_FINE_LOCATION + ) + PermissionGroup.CONNECT -> listOf( + Manifest.permission.BLUETOOTH + ) + } + } + } + + /** + * Quick check if Bluetooth adapter is available. + */ + fun isBluetoothSupported(context: Context): Boolean { + val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? android.bluetooth.BluetoothManager + return manager?.adapter != null + } + + /** + * Check if Bluetooth is currently enabled. + */ + fun isBluetoothEnabled(context: Context): Boolean { + val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? android.bluetooth.BluetoothManager + return manager?.adapter?.isEnabled == true + } +} diff --git a/android/src/main/kotlin/com/bleplx/ScanManager.kt b/android/src/main/kotlin/com/bleplx/ScanManager.kt new file mode 100644 index 000000000..08bfacec2 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/ScanManager.kt @@ -0,0 +1,163 @@ +package com.bleplx + +import android.content.Context +import android.os.ParcelUuid +import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat +import no.nordicsemi.android.support.v18.scanner.ScanCallback +import no.nordicsemi.android.support.v18.scanner.ScanFilter +import no.nordicsemi.android.support.v18.scanner.ScanResult +import no.nordicsemi.android.support.v18.scanner.ScanSettings +import java.util.concurrent.ConcurrentLinkedDeque + +/** + * Wraps Nordic Scanner Compat with throttle debouncing. + * + * Android enforces a hard limit of 5 scan starts in 30 seconds. + * This manager tracks timestamps and rejects early if the limit + * would be hit, and spaces restarts by at least 6 seconds. + */ +class ScanManager(private val context: Context) { + + private val scanner: BluetoothLeScannerCompat = BluetoothLeScannerCompat.getScanner() + private var currentCallback: ScanCallback? = null + private val scanStartTimestamps = ConcurrentLinkedDeque() + + companion object { + private const val MAX_STARTS_IN_WINDOW = 5 + private const val WINDOW_MS = 30_000L + private const val MIN_RESTART_INTERVAL_MS = 6_000L + } + + interface ScanListener { + fun onScanResult(result: ScanResult) + fun onScanFailed(errorCode: Int) + } + + /** + * Start a BLE scan. + * + * @param uuids Optional list of service UUID strings to filter by + * @param scanMode ScanSettings scan mode (0=low power, 1=balanced, 2=low latency) + * @param callbackType ScanSettings callback type + * @param legacyScan Whether to use legacy scanning + * @param listener Callback for results + * @return null on success, or a BleErrorInfo if throttled/failed + */ + fun startScan( + uuids: List?, + scanMode: Int, + callbackType: Int, + legacyScan: Boolean, + listener: ScanListener + ): ErrorConverter.BleErrorInfo? { + // Check permissions + if (!PermissionHelper.hasPermissions(context, PermissionHelper.PermissionGroup.SCAN)) { + return ErrorConverter.BleErrorInfo( + code = ErrorConverter.CONNECT_PERMISSION_DENIED, + message = "Missing BLUETOOTH_SCAN permission", + isRetryable = false + ) + } + + // Check throttle limit + if (isThrottled()) { + return ErrorConverter.scanThrottled() + } + + // Stop any existing scan + stopScan() + + // Build scan filters + val filters = mutableListOf() + uuids?.forEach { uuid -> + try { + filters.add( + ScanFilter.Builder() + .setServiceUuid(ParcelUuid.fromString(uuid)) + .build() + ) + } catch (_: Exception) { + // Skip invalid UUIDs + } + } + + // Build scan settings + val settingsBuilder = ScanSettings.Builder() + .setScanMode(scanMode) + .setCallbackType(callbackType) + .setLegacy(legacyScan) + .setUseHardwareBatchingIfSupported(false) + + val callback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + listener.onScanResult(result) + } + + override fun onScanFailed(errorCode: Int) { + listener.onScanFailed(errorCode) + } + } + currentCallback = callback + + return try { + recordScanStart() + scanner.startScan( + if (filters.isEmpty()) null else filters, + settingsBuilder.build(), + callback + ) + null // success + } catch (e: SecurityException) { + ErrorConverter.fromException(e, operation = "startScan") + } catch (e: Exception) { + ErrorConverter.BleErrorInfo( + code = ErrorConverter.SCAN_START_FAILED, + message = e.message ?: "Failed to start scan", + isRetryable = false + ) + } + } + + fun stopScan() { + currentCallback?.let { + try { + scanner.stopScan(it) + } catch (_: Exception) { + // Ignore errors on stop + } + currentCallback = null + } + } + + val isScanning: Boolean + get() = currentCallback != null + + /** + * Check if starting a new scan would exceed the 5-in-30s limit. + */ + private fun isThrottled(): Boolean { + val now = System.currentTimeMillis() + + // Remove timestamps older than the window + while (scanStartTimestamps.isNotEmpty() && now - scanStartTimestamps.peekFirst()!! > WINDOW_MS) { + scanStartTimestamps.pollFirst() + } + + // Check count + if (scanStartTimestamps.size >= MAX_STARTS_IN_WINDOW) { + return true + } + + // Check minimum interval since last start + val lastStart = scanStartTimestamps.peekLast() + if (lastStart != null && now - lastStart < MIN_RESTART_INTERVAL_MS) { + return true + } + + return false + } + + private fun recordScanStart() { + scanStartTimestamps.addLast(System.currentTimeMillis()) + } +} diff --git a/android/src/test/kotlin/com/bleplx/ErrorConverterTest.kt b/android/src/test/kotlin/com/bleplx/ErrorConverterTest.kt new file mode 100644 index 000000000..dcef0a671 --- /dev/null +++ b/android/src/test/kotlin/com/bleplx/ErrorConverterTest.kt @@ -0,0 +1,269 @@ +package com.bleplx + +import android.bluetooth.BluetoothGatt +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +/** + * Tests for GATT status code → BleErrorCode mapping in ErrorConverter. + */ +class ErrorConverterTest { + + // --- GATT 133 (0x85) → DeviceConnectionFailed + isRetryable=true --- + + @Test + fun `GATT 0x85 (133) maps to DEVICE_CONNECTION_FAILED`() { + val error = ErrorConverter.fromGattStatus(0x85) + assertEquals(ErrorConverter.DEVICE_CONNECTION_FAILED, error.code) + } + + @Test + fun `GATT 0x85 isRetryable is true`() { + val error = ErrorConverter.fromGattStatus(0x85) + assertTrue(error.isRetryable) + } + + @Test + fun `GATT 0x85 preserves gattStatus`() { + val error = ErrorConverter.fromGattStatus(0x85) + assertEquals(0x85, error.gattStatus) + } + + // --- GATT 19 (0x13) → DeviceDisconnected + isRetryable=false --- + + @Test + fun `GATT 0x13 (19) maps to DEVICE_DISCONNECTED`() { + val error = ErrorConverter.fromGattStatus(0x13) + assertEquals(ErrorConverter.DEVICE_DISCONNECTED, error.code) + } + + @Test + fun `GATT 0x13 isRetryable is false`() { + val error = ErrorConverter.fromGattStatus(0x13) + assertFalse(error.isRetryable) + } + + // --- GATT 0x3E → DeviceConnectionFailed + isRetryable=true --- + + @Test + fun `GATT 0x3E maps to DEVICE_CONNECTION_FAILED`() { + val error = ErrorConverter.fromGattStatus(0x3E) + assertEquals(ErrorConverter.DEVICE_CONNECTION_FAILED, error.code) + } + + @Test + fun `GATT 0x3E isRetryable is true`() { + val error = ErrorConverter.fromGattStatus(0x3E) + assertTrue(error.isRetryable) + } + + // --- GATT 0x16 → DeviceDisconnected + isRetryable=true --- + + @Test + fun `GATT 0x16 (22) maps to DEVICE_DISCONNECTED`() { + val error = ErrorConverter.fromGattStatus(0x16) + assertEquals(ErrorConverter.DEVICE_DISCONNECTED, error.code) + } + + @Test + fun `GATT 0x16 isRetryable is true`() { + val error = ErrorConverter.fromGattStatus(0x16) + assertTrue(error.isRetryable) + } + + // --- GATT 0x08 (timeout) --- + + @Test + fun `GATT 0x08 maps to OPERATION_TIMED_OUT and is retryable`() { + val error = ErrorConverter.fromGattStatus(0x08) + assertEquals(ErrorConverter.OPERATION_TIMED_OUT, error.code) + assertTrue(error.isRetryable) + } + + // --- GATT 0x22 (LMP timeout) --- + + @Test + fun `GATT 0x22 maps to DEVICE_CONNECTION_FAILED and is retryable`() { + val error = ErrorConverter.fromGattStatus(0x22) + assertEquals(ErrorConverter.DEVICE_CONNECTION_FAILED, error.code) + assertTrue(error.isRetryable) + } + + // --- GATT 0x05 (insufficient authentication) --- + + @Test + fun `GATT 0x05 maps to BLUETOOTH_UNAUTHORIZED and is not retryable`() { + val error = ErrorConverter.fromGattStatus(0x05) + assertEquals(ErrorConverter.BLUETOOTH_UNAUTHORIZED, error.code) + assertFalse(error.isRetryable) + } + + // --- GATT_SUCCESS (0) treated as error --- + + @Test + fun `GATT_SUCCESS maps to UNKNOWN_ERROR`() { + val error = ErrorConverter.fromGattStatus(BluetoothGatt.GATT_SUCCESS) + assertEquals(ErrorConverter.UNKNOWN_ERROR, error.code) + assertFalse(error.isRetryable) + } + + // --- Unknown GATT status --- + + @Test + fun `Unknown GATT status maps to UNKNOWN_ERROR and is not retryable`() { + val error = ErrorConverter.fromGattStatus(0xFF) + assertEquals(ErrorConverter.UNKNOWN_ERROR, error.code) + assertFalse(error.isRetryable) + assertEquals(0xFF, error.gattStatus) + } + + // --- Context forwarding --- + + @Test + fun `fromGattStatus forwards deviceId and operation`() { + val error = ErrorConverter.fromGattStatus(0x85, deviceId = "AA:BB:CC", operation = "connect") + assertEquals("AA:BB:CC", error.deviceId) + assertEquals("connect", error.operation) + } + + // --- SecurityException → ConnectPermissionDenied --- + + @Test + fun `SecurityException maps to CONNECT_PERMISSION_DENIED`() { + val error = ErrorConverter.fromException(SecurityException("Missing permission")) + assertEquals(ErrorConverter.CONNECT_PERMISSION_DENIED, error.code) + } + + @Test + fun `SecurityException isRetryable is false`() { + val error = ErrorConverter.fromException(SecurityException("Missing permission")) + assertFalse(error.isRetryable) + } + + @Test + fun `SecurityException sets nativeDomain to SecurityException`() { + val error = ErrorConverter.fromException(SecurityException("test")) + assertEquals("SecurityException", error.nativeDomain) + } + + // --- IllegalStateException → BluetoothManagerDestroyed --- + + @Test + fun `IllegalStateException maps to BLUETOOTH_MANAGER_DESTROYED`() { + val error = ErrorConverter.fromException(IllegalStateException("BT destroyed")) + assertEquals(ErrorConverter.BLUETOOTH_MANAGER_DESTROYED, error.code) + assertFalse(error.isRetryable) + } + + // --- Generic exception --- + + @Test + fun `Generic exception maps to UNKNOWN_ERROR`() { + val error = ErrorConverter.fromException(RuntimeException("oops")) + assertEquals(ErrorConverter.UNKNOWN_ERROR, error.code) + assertFalse(error.isRetryable) + assertEquals("RuntimeException", error.nativeDomain) + } + + // --- Scan callback errors 1-6 → ScanFailed --- + + @Test + fun `scan error code 1 maps to SCAN_START_FAILED`() { + val error = ErrorConverter.fromScanError(1) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertEquals(1, error.nativeCode) + assertFalse(error.isRetryable) + } + + @Test + fun `scan error code 2 maps to SCAN_START_FAILED`() { + val error = ErrorConverter.fromScanError(2) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertFalse(error.isRetryable) + } + + @Test + fun `scan error code 3 maps to SCAN_START_FAILED`() { + val error = ErrorConverter.fromScanError(3) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertFalse(error.isRetryable) + } + + @Test + fun `scan error code 4 maps to SCAN_START_FAILED`() { + val error = ErrorConverter.fromScanError(4) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertFalse(error.isRetryable) + } + + @Test + fun `scan error code 5 maps to SCAN_START_FAILED`() { + val error = ErrorConverter.fromScanError(5) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertFalse(error.isRetryable) + } + + @Test + fun `scan error code 6 (too frequently) maps to SCAN_START_FAILED and is retryable`() { + val error = ErrorConverter.fromScanError(6) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertTrue(error.isRetryable, "Error 6 (scanning too frequently) should be retryable") + assertEquals(6, error.nativeCode) + } + + @Test + fun `scan error code unknown maps to SCAN_START_FAILED and is not retryable`() { + val error = ErrorConverter.fromScanError(99) + assertEquals(ErrorConverter.SCAN_START_FAILED, error.code) + assertFalse(error.isRetryable) + assertEquals(99, error.nativeCode) + } + + // --- BleErrorInfo data class integrity --- + + @Test + fun `BleErrorInfo holds all fields`() { + val info = ErrorConverter.BleErrorInfo( + code = ErrorConverter.DEVICE_CONNECTION_FAILED, + message = "test", + isRetryable = true, + deviceId = "dev-1", + serviceUuid = "svc-uuid", + characteristicUuid = "char-uuid", + operation = "connect", + nativeDomain = "Domain", + nativeCode = 42, + gattStatus = 133 + ) + assertEquals(ErrorConverter.DEVICE_CONNECTION_FAILED, info.code) + assertEquals("test", info.message) + assertTrue(info.isRetryable) + assertEquals("dev-1", info.deviceId) + assertEquals("svc-uuid", info.serviceUuid) + assertEquals("char-uuid", info.characteristicUuid) + assertEquals("connect", info.operation) + assertEquals("Domain", info.nativeDomain) + assertEquals(42, info.nativeCode) + assertEquals(133, info.gattStatus) + } + + @Test + fun `BleErrorInfo optional fields default to null`() { + val info = ErrorConverter.BleErrorInfo( + code = ErrorConverter.UNKNOWN_ERROR, + message = "minimal", + isRetryable = false + ) + assertNull(info.deviceId) + assertNull(info.serviceUuid) + assertNull(info.characteristicUuid) + assertNull(info.operation) + assertNull(info.nativeDomain) + assertNull(info.nativeCode) + assertNull(info.gattStatus) + } +} diff --git a/android/src/test/kotlin/com/bleplx/EventSerializerTest.kt b/android/src/test/kotlin/com/bleplx/EventSerializerTest.kt new file mode 100644 index 000000000..297cee498 --- /dev/null +++ b/android/src/test/kotlin/com/bleplx/EventSerializerTest.kt @@ -0,0 +1,453 @@ +package com.bleplx + +import android.bluetooth.BluetoothGattCharacteristic +import android.util.Base64 +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.UUID + +/** + * Tests for EventSerializer — native BLE object → JS event map serialization. + * + * React Native's Arguments / WritableMap are Android-only, so we mock them + * using MockK statics + JavaOnlyMap/JavaOnlyArray (available in react-android + * test artefacts). Base64 is also mocked since it's an Android SDK class. + */ +class EventSerializerTest { + + @BeforeEach + fun setup() { + mockkStatic(Arguments::class) + every { Arguments.createMap() } answers { JavaOnlyMap() } + every { Arguments.createArray() } answers { JavaOnlyArray() } + + mockkStatic(Base64::class) + // For testing, just return a predictable string rather than real Base64 + every { Base64.encodeToString(any(), any()) } answers { + java.util.Base64.getEncoder().encodeToString(firstArg()) + } + } + + @AfterEach + fun teardown() { + unmockkAll() + } + + // ---------- ScanResult ---------- + + @Test + fun `serializeScanResult includes id field`() { + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = "TestDevice", + rssi = -70, + serviceUuids = emptyList(), + manufacturerData = null + ) + assertEquals("AA:BB:CC:DD:EE:FF", map.getString("id")) + } + + @Test + fun `serializeScanResult includes name when non-null`() { + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = "MySensor", + rssi = -65, + serviceUuids = emptyList(), + manufacturerData = null + ) + assertEquals("MySensor", map.getString("name")) + } + + @Test + fun `serializeScanResult has null name when deviceName is null`() { + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = null, + rssi = -65, + serviceUuids = emptyList(), + manufacturerData = null + ) + assertTrue(map.isNull("name")) + } + + @Test + fun `serializeScanResult includes rssi`() { + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = null, + rssi = -85, + serviceUuids = emptyList(), + manufacturerData = null + ) + assertEquals(-85, map.getInt("rssi")) + } + + @Test + fun `serializeScanResult includes serviceUuids array`() { + val uuids = listOf("0000180A-0000-1000-8000-00805F9B34FB") + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = null, + rssi = -70, + serviceUuids = uuids, + manufacturerData = null + ) + val array = map.getArray("serviceUuids") + assertNotNull(array) + assertEquals(1, array!!.size()) + assertEquals("0000180A-0000-1000-8000-00805F9B34FB", array.getString(0)) + } + + @Test + fun `serializeScanResult has empty serviceUuids array when none provided`() { + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = null, + rssi = -70, + serviceUuids = emptyList(), + manufacturerData = null + ) + val array = map.getArray("serviceUuids") + assertNotNull(array) + assertEquals(0, array!!.size()) + } + + @Test + fun `serializeScanResult encodes manufacturerData as Base64`() { + val data = byteArrayOf(0x01, 0x02, 0x03) + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = null, + rssi = -70, + serviceUuids = emptyList(), + manufacturerData = data + ) + val encoded = map.getString("manufacturerData") + assertNotNull(encoded) + assertEquals(java.util.Base64.getEncoder().encodeToString(data), encoded) + } + + @Test + fun `serializeScanResult has null manufacturerData when not provided`() { + val map = EventSerializer.serializeScanResult( + deviceAddress = "AA:BB:CC:DD:EE:FF", + deviceName = null, + rssi = -70, + serviceUuids = emptyList(), + manufacturerData = null + ) + assertTrue(map.isNull("manufacturerData")) + } + + // ---------- DeviceInfo ---------- + + @Test + fun `serializeDeviceInfo includes all required fields`() { + val map = EventSerializer.serializeDeviceInfo( + deviceAddress = "11:22:33:44:55:66", + deviceName = "Hub", + rssi = -60, + mtu = 512, + isConnectable = true, + serviceUuids = listOf("180D"), + manufacturerData = null + ) + assertEquals("11:22:33:44:55:66", map.getString("id")) + assertEquals("Hub", map.getString("name")) + assertEquals(-60, map.getInt("rssi")) + assertEquals(512, map.getInt("mtu")) + assertTrue(map.getBoolean("isConnectable")) + assertNotNull(map.getArray("serviceUuids")) + } + + @Test + fun `serializeDeviceInfo has null name when not provided`() { + val map = EventSerializer.serializeDeviceInfo( + deviceAddress = "11:22:33:44:55:66", + deviceName = null + ) + assertTrue(map.isNull("name")) + } + + @Test + fun `serializeDeviceInfo defaults rssi to 0`() { + val map = EventSerializer.serializeDeviceInfo( + deviceAddress = "11:22:33:44:55:66", + deviceName = null + ) + assertEquals(0, map.getInt("rssi")) + } + + @Test + fun `serializeDeviceInfo defaults mtu to 23`() { + val map = EventSerializer.serializeDeviceInfo( + deviceAddress = "11:22:33:44:55:66", + deviceName = null + ) + assertEquals(23, map.getInt("mtu")) + } + + @Test + fun `serializeDeviceInfo has null isConnectable when not provided`() { + val map = EventSerializer.serializeDeviceInfo( + deviceAddress = "11:22:33:44:55:66", + deviceName = null, + isConnectable = null + ) + assertTrue(map.isNull("isConnectable")) + } + + @Test + fun `serializeDeviceInfo has empty serviceUuids by default`() { + val map = EventSerializer.serializeDeviceInfo( + deviceAddress = "11:22:33:44:55:66", + deviceName = null + ) + val array = map.getArray("serviceUuids") + assertNotNull(array) + assertEquals(0, array!!.size()) + } + + // ---------- CharacteristicInfo (simple variant) ---------- + + @Test + fun `serializeCharacteristicSimple includes deviceId and uuids`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc-uuid", + characteristicUuid = "char-uuid", + value = null + ) + assertEquals("dev-1", map.getString("deviceId")) + assertEquals("svc-uuid", map.getString("serviceUuid")) + assertEquals("char-uuid", map.getString("uuid")) + } + + @Test + fun `serializeCharacteristicSimple has null value when not provided`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null + ) + assertTrue(map.isNull("value")) + } + + @Test + fun `serializeCharacteristicSimple encodes value as Base64`() { + val data = byteArrayOf(0xDE.toByte(), 0xAD.toByte(), 0xBE.toByte(), 0xEF.toByte()) + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = data + ) + val encoded = map.getString("value") + assertNotNull(encoded) + assertEquals(java.util.Base64.getEncoder().encodeToString(data), encoded) + } + + @Test + fun `serializeCharacteristicSimple isNotifying defaults to false`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null + ) + assertFalse(map.getBoolean("isNotifying")) + } + + @Test + fun `serializeCharacteristicSimple isNotifying reflects passed value`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + isNotifying = true + ) + assertTrue(map.getBoolean("isNotifying")) + } + + @Test + fun `serializeCharacteristicSimple properties default to false when 0`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + properties = 0 + ) + assertFalse(map.getBoolean("isIndicatable")) + assertFalse(map.getBoolean("isReadable")) + assertFalse(map.getBoolean("isWritableWithResponse")) + assertFalse(map.getBoolean("isWritableWithoutResponse")) + } + + @Test + fun `serializeCharacteristicSimple isReadable is true when READ property set`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + properties = BluetoothGattCharacteristic.PROPERTY_READ + ) + assertTrue(map.getBoolean("isReadable")) + assertFalse(map.getBoolean("isWritableWithResponse")) + } + + @Test + fun `serializeCharacteristicSimple isWritableWithResponse is true when WRITE property set`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + properties = BluetoothGattCharacteristic.PROPERTY_WRITE + ) + assertTrue(map.getBoolean("isWritableWithResponse")) + assertFalse(map.getBoolean("isReadable")) + } + + @Test + fun `serializeCharacteristicSimple isWritableWithoutResponse is true when WRITE_NO_RESPONSE set`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + properties = BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + ) + assertTrue(map.getBoolean("isWritableWithoutResponse")) + } + + @Test + fun `serializeCharacteristicSimple isIndicatable is true when INDICATE property set`() { + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + properties = BluetoothGattCharacteristic.PROPERTY_INDICATE + ) + assertTrue(map.getBoolean("isIndicatable")) + } + + @Test + fun `serializeCharacteristicSimple handles combined properties`() { + val props = BluetoothGattCharacteristic.PROPERTY_READ or + BluetoothGattCharacteristic.PROPERTY_WRITE or + BluetoothGattCharacteristic.PROPERTY_NOTIFY + + val map = EventSerializer.serializeCharacteristicSimple( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = null, + properties = props + ) + assertTrue(map.getBoolean("isReadable")) + assertTrue(map.getBoolean("isWritableWithResponse")) + assertFalse(map.getBoolean("isIndicatable")) + } + + // ---------- ConnectionStateEvent ---------- + + @Test + fun `serializeConnectionStateEvent includes deviceId and state`() { + val map = EventSerializer.serializeConnectionStateEvent( + deviceId = "dev-1", + state = "connected" + ) + assertEquals("dev-1", map.getString("deviceId")) + assertEquals("connected", map.getString("state")) + } + + @Test + fun `serializeConnectionStateEvent has null errorCode and errorMessage when omitted`() { + val map = EventSerializer.serializeConnectionStateEvent( + deviceId = "dev-1", + state = "disconnected" + ) + assertTrue(map.isNull("errorCode")) + assertTrue(map.isNull("errorMessage")) + } + + @Test + fun `serializeConnectionStateEvent includes errorCode and errorMessage when provided`() { + val map = EventSerializer.serializeConnectionStateEvent( + deviceId = "dev-1", + state = "disconnected", + errorCode = 201, + errorMessage = "peer disconnected" + ) + assertEquals(201, map.getInt("errorCode")) + assertEquals("peer disconnected", map.getString("errorMessage")) + } + + // ---------- CharacteristicValueEvent ---------- + + @Test + fun `serializeCharacteristicValueEvent includes all fields`() { + val value = byteArrayOf(0x01, 0x02) + val map = EventSerializer.serializeCharacteristicValueEvent( + deviceId = "dev-1", + serviceUuid = "svc-uuid", + characteristicUuid = "char-uuid", + value = value, + transactionId = "txn-42" + ) + assertEquals("dev-1", map.getString("deviceId")) + assertEquals("svc-uuid", map.getString("serviceUuid")) + assertEquals("char-uuid", map.getString("characteristicUuid")) + assertEquals("txn-42", map.getString("transactionId")) + assertNotNull(map.getString("value")) + } + + @Test + fun `serializeCharacteristicValueEvent has null transactionId when not provided`() { + val map = EventSerializer.serializeCharacteristicValueEvent( + deviceId = "dev-1", + serviceUuid = "svc", + characteristicUuid = "char", + value = byteArrayOf(0xFF.toByte()) + ) + assertTrue(map.isNull("transactionId")) + } + + // ---------- StateChangeEvent ---------- + + @Test + fun `serializeStateChangeEvent includes state field`() { + val map = EventSerializer.serializeStateChangeEvent("PoweredOn") + assertEquals("PoweredOn", map.getString("state")) + } + + @Test + fun `serializeStateChangeEvent handles all expected state strings`() { + val states = listOf("Unknown", "Resetting", "Unsupported", "Unauthorized", "PoweredOff", "PoweredOn") + states.forEach { state -> + val map = EventSerializer.serializeStateChangeEvent(state) + assertEquals(state, map.getString("state"), "State '$state' should round-trip correctly") + } + } +} diff --git a/android/src/test/kotlin/com/bleplx/PermissionHelperTest.kt b/android/src/test/kotlin/com/bleplx/PermissionHelperTest.kt new file mode 100644 index 000000000..696cf8ad4 --- /dev/null +++ b/android/src/test/kotlin/com/bleplx/PermissionHelperTest.kt @@ -0,0 +1,202 @@ +package com.bleplx + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.ContextCompat +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import android.content.Context +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +/** + * Tests for SDK version gating in PermissionHelper. + * + * API < 31 → BLUETOOTH + ACCESS_FINE_LOCATION for SCAN + * BLUETOOTH for CONNECT + * API 31+ → BLUETOOTH_SCAN for SCAN + * BLUETOOTH_CONNECT for CONNECT + * + * Build.VERSION.SDK_INT is a final field; we test getRequiredPermissions() + * directly by inspecting what the production constant resolves to on the + * test JVM, and test the hasPermissions / getMissingPermissions logic with + * a mocked Context + ContextCompat. + */ +class PermissionHelperTest { + + private lateinit var mockContext: Context + + @BeforeEach + fun setup() { + mockContext = mockk(relaxed = true) + mockkStatic(ContextCompat::class) + } + + @AfterEach + fun teardown() { + unmockkAll() + } + + // --- getRequiredPermissions on current SDK --- + + @Test + fun `getRequiredPermissions SCAN returns non-empty list`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + assertTrue(perms.isNotEmpty()) + } + + @Test + fun `getRequiredPermissions CONNECT returns non-empty list`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.CONNECT) + assertTrue(perms.isNotEmpty()) + } + + @Test + fun `SCAN permissions on API 31+ include BLUETOOTH_SCAN`() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + assertTrue(perms.contains(Manifest.permission.BLUETOOTH_SCAN), + "API 31+ SCAN must include BLUETOOTH_SCAN, got: $perms") + assertFalse(perms.contains(Manifest.permission.ACCESS_FINE_LOCATION), + "API 31+ SCAN must not require location") + } + } + + @Test + fun `CONNECT permissions on API 31+ include BLUETOOTH_CONNECT`() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.CONNECT) + assertTrue(perms.contains(Manifest.permission.BLUETOOTH_CONNECT), + "API 31+ CONNECT must include BLUETOOTH_CONNECT, got: $perms") + } + } + + @Test + fun `SCAN permissions on API below 31 include BLUETOOTH and location`() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + assertTrue(perms.contains(Manifest.permission.BLUETOOTH), + "API < 31 SCAN must include BLUETOOTH, got: $perms") + assertTrue(perms.contains(Manifest.permission.ACCESS_FINE_LOCATION), + "API < 31 SCAN must include ACCESS_FINE_LOCATION, got: $perms") + } + } + + @Test + fun `CONNECT permissions on API below 31 include BLUETOOTH`() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.CONNECT) + assertTrue(perms.contains(Manifest.permission.BLUETOOTH), + "API < 31 CONNECT must include BLUETOOTH, got: $perms") + } + } + + // --- hasPermissions / getMissingPermissions with mocked ContextCompat --- + + @Test + fun `hasPermissions returns true when all permissions are granted`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + perms.forEach { perm -> + every { ContextCompat.checkSelfPermission(mockContext, perm) } returns PackageManager.PERMISSION_GRANTED + } + + assertTrue(PermissionHelper.hasPermissions(mockContext, PermissionHelper.PermissionGroup.SCAN)) + } + + @Test + fun `hasPermissions returns false when any permission is denied`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + // Grant all but the first + perms.forEachIndexed { index, perm -> + val result = if (index == 0) PackageManager.PERMISSION_DENIED else PackageManager.PERMISSION_GRANTED + every { ContextCompat.checkSelfPermission(mockContext, perm) } returns result + } + + assertFalse(PermissionHelper.hasPermissions(mockContext, PermissionHelper.PermissionGroup.SCAN)) + } + + @Test + fun `getMissingPermissions returns empty list when all granted`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.CONNECT) + perms.forEach { perm -> + every { ContextCompat.checkSelfPermission(mockContext, perm) } returns PackageManager.PERMISSION_GRANTED + } + + val missing = PermissionHelper.getMissingPermissions(mockContext, PermissionHelper.PermissionGroup.CONNECT) + assertTrue(missing.isEmpty(), "Expected no missing permissions, got: $missing") + } + + @Test + fun `getMissingPermissions returns denied permissions`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + perms.forEach { perm -> + every { ContextCompat.checkSelfPermission(mockContext, perm) } returns PackageManager.PERMISSION_DENIED + } + + val missing = PermissionHelper.getMissingPermissions(mockContext, PermissionHelper.PermissionGroup.SCAN) + assertEquals(perms.size, missing.size, "All permissions should be missing") + assertTrue(missing.containsAll(perms)) + } + + @Test + fun `getMissingPermissions only returns actually denied permissions`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + if (perms.size >= 2) { + every { ContextCompat.checkSelfPermission(mockContext, perms[0]) } returns PackageManager.PERMISSION_DENIED + every { ContextCompat.checkSelfPermission(mockContext, perms[1]) } returns PackageManager.PERMISSION_GRANTED + + val missing = PermissionHelper.getMissingPermissions(mockContext, PermissionHelper.PermissionGroup.SCAN) + assertEquals(1, missing.size) + assertTrue(missing.contains(perms[0])) + assertFalse(missing.contains(perms[1])) + } + } + + @Test + fun `hasPermissions SCAN returns false when all denied`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + perms.forEach { perm -> + every { ContextCompat.checkSelfPermission(mockContext, perm) } returns PackageManager.PERMISSION_DENIED + } + + assertFalse(PermissionHelper.hasPermissions(mockContext, PermissionHelper.PermissionGroup.SCAN)) + } + + @Test + fun `hasPermissions CONNECT returns false when all denied`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.CONNECT) + perms.forEach { perm -> + every { ContextCompat.checkSelfPermission(mockContext, perm) } returns PackageManager.PERMISSION_DENIED + } + + assertFalse(PermissionHelper.hasPermissions(mockContext, PermissionHelper.PermissionGroup.CONNECT)) + } + + // --- API-level specific permission names (parametric style) --- + + @Test + fun `required SCAN permissions are known Android permission strings`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.SCAN) + perms.forEach { perm -> + assertTrue(perm.startsWith("android.permission.") || perm.startsWith("android.permission."), + "Permission '$perm' should be a valid android.permission string") + } + } + + @Test + fun `required CONNECT permissions are known Android permission strings`() { + val perms = PermissionHelper.getRequiredPermissions(PermissionHelper.PermissionGroup.CONNECT) + perms.forEach { perm -> + assertTrue(perm.startsWith("android.permission."), + "Permission '$perm' should be a valid android.permission string") + } + } +} diff --git a/android/src/test/kotlin/com/bleplx/README.md b/android/src/test/kotlin/com/bleplx/README.md new file mode 100644 index 000000000..ee282ea8c --- /dev/null +++ b/android/src/test/kotlin/com/bleplx/README.md @@ -0,0 +1,86 @@ +# Android Unit Tests + +This directory is the standard Gradle unit-test source set for the +`react-native-ble-plx` Android module. Tests here run on the JVM (no emulator +or device required) using JUnit 5 and MockK. + +## Setup + +Add to `android/build.gradle` (already configured if using the v4 module): + +```groovy +android { + testOptions { + unitTests.all { + useJUnitPlatform() // JUnit 5 + } + } +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'io.mockk:mockk:1.13.10' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3' +} +``` + +## Test files to create + +### `BleModuleTest.kt` + +Test the `BlePlxModule` TurboModule: + +- `createClient()` calls `BluetoothAdapter.enable()` on Android < 13 (requires + mock of adapter) and resolves the promise. +- `startDeviceScan()` starts a `BluetoothLeScanner` session with the correct + `ScanSettings` derived from the JS options object. +- `stopDeviceScan()` calls `stopScan()` on the active scanner. +- `connectToDevice()` with `autoConnect: false` opens a GATT connection and + resolves the promise with the serialized `DeviceInfo`. +- `cancelDeviceConnection()` calls `gatt.disconnect()` and `gatt.close()`. +- `writeCharacteristic()` with `withResponse: true` calls + `gatt.writeCharacteristic()` with `WRITE_TYPE_DEFAULT`. +- `writeCharacteristic()` with `withResponse: false` uses + `WRITE_TYPE_NO_RESPONSE`. +- `monitorCharacteristic()` enables notifications (writes to the CCCD) and + sets up the callback plumbing. +- `cancelTransaction()` cancels any in-flight coroutine for that transaction ID. + +### `GattCallbackTest.kt` + +Test the `GattCallback` class that bridges Android GATT callbacks to the +JS event emitter: + +- `onConnectionStateChange(STATE_CONNECTED)` fires `onConnectionStateChange` + event with `state: "connected"`. +- `onConnectionStateChange(STATE_DISCONNECTED)` fires the event with + `state: "disconnected"` and correct `errorCode`/`errorMessage` for non-zero + GATT status codes. +- `onCharacteristicChanged()` fires `onCharacteristicValueUpdate` with the + base64-encoded value. +- `onServicesDiscovered()` resolves the pending `discoverAllServices` promise. + +### `ScanCallbackTest.kt` + +Test the `ScanCallback` that handles `BluetoothLeScanner` results: + +- `onScanResult()` fires `onScanResult` with correct `DeviceInfo` fields. +- `onScanFailed()` fires an error event with `BleErrorCode.ScanFailed`. + +### `BleErrorMapperTest.kt` + +Test the Android → BleErrorCode mapping: + +- GATT status 133 maps to `ConnectionFailed` with `isRetryable: true`. +- GATT status 8 (connection timeout) maps to `ConnectionTimeout`. +- Scan failure code `SCAN_FAILED_FEATURE_UNSUPPORTED` maps to + `OperationNotSupported`. + +## Running + +```sh +cd android +./gradlew :react-native-ble-plx:testDebugUnitTest +``` + +HTML report: `android/build/reports/tests/testDebugUnitTest/index.html` diff --git a/android/src/test/kotlin/com/bleplx/ScanManagerTest.kt b/android/src/test/kotlin/com/bleplx/ScanManagerTest.kt new file mode 100644 index 000000000..5ea929386 --- /dev/null +++ b/android/src/test/kotlin/com/bleplx/ScanManagerTest.kt @@ -0,0 +1,258 @@ +package com.bleplx + +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.atomic.AtomicLong + +/** + * Tests for scan throttle debouncing logic in ScanManager. + * + * ScanManager uses System.currentTimeMillis() directly, so we test through + * a TestableScanManager that allows injecting a controllable clock. + */ +class ScanManagerTest { + + // --- Testable subclass with injectable clock --- + + /** + * Exposes the throttle logic with an injected clock so tests don't depend + * on wall time. We replicate the throttle state here instead of reaching + * into the private fields of the real ScanManager. + */ + private class ThrottleChecker { + companion object { + const val MAX_STARTS_IN_WINDOW = 5 + const val WINDOW_MS = 30_000L + const val MIN_RESTART_INTERVAL_MS = 6_000L + } + + val timestamps = ConcurrentLinkedDeque() + var simulatedIsScanning = false + + /** + * Simulate attempting a scan start at [now]. + * Returns a [ErrorConverter.BleErrorInfo] if throttled, null on success. + */ + fun tryStart(now: Long): ErrorConverter.BleErrorInfo? { + // Remove timestamps outside the window + while (timestamps.isNotEmpty() && now - timestamps.peekFirst()!! > WINDOW_MS) { + timestamps.pollFirst() + } + + // Check count + if (timestamps.size >= MAX_STARTS_IN_WINDOW) { + return ErrorConverter.scanThrottled() + } + + // Check minimum interval + val lastStart = timestamps.peekLast() + if (lastStart != null && now - lastStart < MIN_RESTART_INTERVAL_MS) { + return ErrorConverter.scanThrottled() + } + + // Success — record start + timestamps.addLast(now) + simulatedIsScanning = true + return null + } + + fun stop() { + simulatedIsScanning = false + } + } + + private lateinit var throttle: ThrottleChecker + + @BeforeEach + fun setup() { + throttle = ThrottleChecker() + } + + // --- 5 scans in 30s → 6th returns ScanThrottled --- + + @Test + fun `5 scans within 30s window all succeed`() { + var time = 0L + + for (i in 1..5) { + val result = throttle.tryStart(time) + assertNull(result, "Scan $i should succeed but got error: $result") + time += 6_000L // space each by 6s (exactly the minimum interval) + } + + assertEquals(5, throttle.timestamps.size) + } + + @Test + fun `6th scan within 30s window returns ScanThrottled`() { + var time = 0L + + for (i in 1..5) { + throttle.tryStart(time) + time += 6_000L + } + + // 6th scan at 30s (still within the window since window started at time=0) + val result = throttle.tryStart(time - 1) // just inside the 30s window + assertNotNull(result, "6th scan should be throttled") + assertEquals(ErrorConverter.SCAN_THROTTLED, result!!.code) + assertTrue(result.isRetryable, "ScanThrottled should be retryable") + } + + @Test + fun `scan at window boundary evicts old timestamps and succeeds`() { + var time = 0L + + // Fill 5 slots + for (i in 1..5) { + throttle.tryStart(time) + time += 6_000L + } + + // Jump to just past 30s from the first scan (time=0) + // First timestamp was at 0, window is 30_000ms + time = 30_001L + + val result = throttle.tryStart(time) + assertNull(result, "Scan after window expiry should succeed") + } + + // --- Scans spaced by 6+ seconds all succeed --- + + @Test + fun `scans spaced exactly 6 seconds succeed up to limit`() { + var time = 0L + for (i in 1..5) { + val result = throttle.tryStart(time) + assertNull(result, "Scan $i at time $time should succeed") + time += 6_000L + } + } + + @Test + fun `scan restarted too soon within 6s is throttled`() { + throttle.tryStart(0L) + throttle.stop() + + val result = throttle.tryStart(5_999L) // 1ms short of minimum interval + assertNotNull(result, "Restart within 6s should be throttled") + assertEquals(ErrorConverter.SCAN_THROTTLED, result!!.code) + } + + @Test + fun `scan restarted at exactly 6s succeeds`() { + throttle.tryStart(0L) + throttle.stop() + + val result = throttle.tryStart(6_000L) + assertNull(result, "Restart at exactly 6s should succeed") + } + + @Test + fun `scan restarted after 7s succeeds`() { + throttle.tryStart(0L) + throttle.stop() + + val result = throttle.tryStart(7_000L) + assertNull(result, "Restart after 7s should succeed") + } + + // --- Stop/start cycle counts correctly --- + + @Test + fun `stop and restart still counts as two scan starts`() { + throttle.tryStart(0L) + throttle.stop() + throttle.tryStart(6_000L) + throttle.stop() + throttle.tryStart(12_000L) + throttle.stop() + + assertEquals(3, throttle.timestamps.size, "Three starts should be recorded regardless of stops") + } + + @Test + fun `stop does not remove recorded timestamps`() { + throttle.tryStart(0L) + throttle.stop() + + assertEquals(1, throttle.timestamps.size, "Timestamp must persist after stop") + } + + @Test + fun `repeated stop has no effect on timestamps`() { + throttle.tryStart(0L) + throttle.stop() + throttle.stop() + throttle.stop() + + assertEquals(1, throttle.timestamps.size) + } + + // --- isScanning flag tracks state --- + + @Test + fun `isScanning is false before first start`() { + assertFalse(throttle.simulatedIsScanning) + } + + @Test + fun `isScanning becomes true after successful start`() { + throttle.tryStart(0L) + assertTrue(throttle.simulatedIsScanning) + } + + @Test + fun `isScanning becomes false after stop`() { + throttle.tryStart(0L) + throttle.stop() + assertFalse(throttle.simulatedIsScanning) + } + + @Test + fun `isScanning remains false when throttled`() { + // Fill up 5 slots quickly (all within 6s of each other — first succeeds, second fails) + throttle.tryStart(0L) + val result = throttle.tryStart(1_000L) // too soon + assertNotNull(result) + // The throttle check happens before setting isScanning, so no state change + // But isScanning was set by the first tryStart + assertTrue(throttle.simulatedIsScanning, "Still scanning from first start") + } + + @Test + fun `isScanning is false after throttled attempt when not previously scanning`() { + // Force timestamps to fill without setting isScanning + val now = 0L + for (i in 0..4) { + throttle.timestamps.addLast(now + i * 6_000L) + } + // timestamps is full but simulatedIsScanning was never set + assertFalse(throttle.simulatedIsScanning) + + val result = throttle.tryStart(now + 24_001L) // within window but count exceeded + assertNotNull(result) + assertFalse(throttle.simulatedIsScanning, "isScanning must stay false when throttled") + } + + // --- ErrorConverter.scanThrottled() structure --- + + @Test + fun `scanThrottled error has correct code and is retryable`() { + val error = ErrorConverter.scanThrottled() + assertEquals(ErrorConverter.SCAN_THROTTLED, error.code) + assertTrue(error.isRetryable) + assertTrue(error.message.isNotEmpty()) + } +} diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 000000000..9f1286356 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,1001 @@ +# API Reference + +Complete reference for `BleManager` -- the only class you need to interact with for all BLE operations. + +```typescript +import { BleManager } from 'react-native-ble-plx'; +``` + +--- + +## Table of Contents + +- [Types](#types) +- [Lifecycle](#lifecycle) +- [State](#state) +- [Scanning](#scanning) +- [Connection](#connection) +- [Discovery](#discovery) +- [Read / Write](#read--write) +- [Monitoring](#monitoring) +- [MTU](#mtu) +- [PHY](#phy) +- [Connection Priority](#connection-priority) +- [L2CAP](#l2cap) +- [Bonding](#bonding) +- [Authorization](#authorization) +- [Events](#events) +- [Cancellation](#cancellation) +- [Error Codes](#error-codes) + +--- + +## Types + +### `State` + +Bluetooth adapter state. + +```typescript +const State = { + Unknown: 'Unknown', + Resetting: 'Resetting', + Unsupported: 'Unsupported', + Unauthorized: 'Unauthorized', + PoweredOff: 'PoweredOff', + PoweredOn: 'PoweredOn', +} as const; +type State = (typeof State)[keyof typeof State]; +``` + +### `ConnectionPriority` + +Android connection priority levels. iOS ignores these. + +```typescript +const ConnectionPriority = { + Balanced: 0, + High: 1, + LowPower: 2, +} as const; +type ConnectionPriority = (typeof ConnectionPriority)[keyof typeof ConnectionPriority]; +``` + +### `ConnectionState` + +```typescript +const ConnectionState = { + Disconnected: 'disconnected', + Connecting: 'connecting', + Connected: 'connected', + Disconnecting: 'disconnecting', +} as const; +type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState]; +``` + +### `LogLevel` + +Exported for forward compatibility, but currently unused. There is no `setLogLevel()` method in v4 -- the native modules handle their own logging. This may become functional in a future release. + +```typescript +const LogLevel = { + None: 'None', + Verbose: 'Verbose', + Debug: 'Debug', + Info: 'Info', + Warning: 'Warning', + Error: 'Error', +} as const; +type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; +``` + +### `ScanOptions` + +Options for `startDeviceScan`. + +```typescript +interface ScanOptions { + scanMode?: number; // Android: 0=opportunistic, 1=lowPower, 2=balanced, -1=lowLatency + callbackType?: number; // Android: 1=allMatches, 2=firstMatch, 4=matchLost + legacyScan?: boolean; // false = BLE 5.0 extended advertising + allowDuplicates?: boolean; // iOS only +} +``` + +### `ConnectOptions` + +Options for `connectToDevice`. + +```typescript +interface ConnectOptions { + autoConnect?: boolean; // Android: true=background connect, false=direct (default) + timeout?: number; // Connection timeout in ms + retries?: number; // Retry attempts (default 1 = no retry) + retryDelay?: number; // Ms between retries (default 1000) + requestMtu?: number; // Request MTU after connect (default 517 on Android) +} +``` + +### `MonitorOptions` + +Options for `monitorCharacteristicForDevice`. + +```typescript +interface MonitorOptions { + transactionId?: string; + batchInterval?: number; // 0 = immediate, >0 = batch in ms + subscriptionType?: 'notify' | 'indicate' | null; // null = auto-detect +} +``` + +### `BleManagerOptions` + +Constructor options for `BleManager`. + +```typescript +interface BleManagerOptions { + scanBatchIntervalMs?: number; // Default: 100ms +} +``` + +### `DeviceInfo` + +Returned by connection, discovery, and scan operations. + +```typescript +interface DeviceInfo { + readonly id: string; // Android: MAC address. iOS: opaque UUID. + readonly name: string | null; + readonly rssi: number; + readonly mtu: number; + readonly isConnectable: boolean | null; + readonly serviceUuids: readonly string[]; + readonly manufacturerData: string | null; // Base64-encoded +} +``` + +### `CharacteristicInfo` + +Returned by read, write, and monitor operations. + +```typescript +interface CharacteristicInfo { + readonly deviceId: string; + readonly serviceUuid: string; + readonly uuid: string; + readonly value: string | null; // Base64-encoded + readonly isNotifying: boolean; + readonly isIndicatable: boolean; + readonly isReadable: boolean; + readonly isWritableWithResponse: boolean; + readonly isWritableWithoutResponse: boolean; +} +``` + +### `ScanResult` + +Emitted during scanning. + +```typescript +interface ScanResult { + readonly id: string; + readonly name: string | null; + readonly rssi: number; + readonly serviceUuids: readonly string[]; + readonly manufacturerData: string | null; // Base64-encoded +} +``` + +### `Subscription` + +Returned by all event-listening methods. Call `.remove()` to unsubscribe. + +```typescript +interface Subscription { + remove(): void; +} +``` + +### `ConnectionStateEvent` + +```typescript +interface ConnectionStateEvent { + readonly deviceId: string; + readonly state: string; + readonly errorCode: number | null; + readonly errorMessage: string | null; +} +``` + +### `CharacteristicValueEvent` + +```typescript +interface CharacteristicValueEvent { + readonly deviceId: string; + readonly serviceUuid: string; + readonly characteristicUuid: string; + readonly value: string; // Base64-encoded + readonly transactionId: string | null; +} +``` + +### `BondStateEvent` + +```typescript +interface BondStateEvent { + readonly deviceId: string; + readonly bondState: string; // 'none' | 'bonding' | 'bonded' +} +``` + +### `ConnectionEvent` + +```typescript +interface ConnectionEvent { + readonly deviceId: string; + readonly connectionState: string; +} +``` + +### `RestoreStateEvent` + +```typescript +interface RestoreStateEvent { + readonly devices: readonly DeviceInfo[]; +} +``` + +### `L2CAPChannelEvent` + +```typescript +interface L2CAPChannelEvent { + readonly channelId: number; + readonly deviceId: string; + readonly psm: number; +} +``` + +### `PhyInfo` + +```typescript +interface PhyInfo { + readonly deviceId: string; + readonly txPhy: number; + readonly rxPhy: number; +} +``` + +--- + +## Lifecycle + +### `constructor(options?: BleManagerOptions)` + +Creates a new `BleManager` instance. + +```typescript +const manager = new BleManager(); +// or with options: +const manager = new BleManager({ scanBatchIntervalMs: 200 }); +``` + +**Parameters:** + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `options.scanBatchIntervalMs` | `number` | `100` | How often scan results are batched and delivered to your callback, in milliseconds. | + +--- + +### `createClient(restoreStateIdentifier?: string | null): Promise` + +Initializes the native BLE client. Must be called before any other BLE operations. + +```typescript +await manager.createClient(); +// With state restoration (iOS): +await manager.createClient('my-app-ble-restore-id'); +``` + +**Parameters:** + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `restoreStateIdentifier` | `string \| null` | `null` | iOS state restoration identifier. Pass a consistent string to enable background restoration. | + +Calling `createClient` again invalidates the previous client and creates a fresh one. + +--- + +### `destroyClient(): Promise` + +Tears down the native BLE client and cleans up all resources: active scans, monitor subscriptions, pending transactions, event listeners. + +```typescript +await manager.destroyClient(); +``` + +Call this when your app no longer needs BLE, or before calling `createClient()` again. + +--- + +## State + +### `state(): Promise` + +Returns the current Bluetooth adapter state. + +```typescript +const currentState = await manager.state(); +if (currentState === State.PoweredOn) { + // Ready to go +} +``` + +**Returns:** `Promise` -- one of `'Unknown'`, `'Resetting'`, `'Unsupported'`, `'Unauthorized'`, `'PoweredOff'`, `'PoweredOn'`. + +--- + +### `onStateChange(callback, emitCurrentState?): Subscription` + +Subscribes to Bluetooth adapter state changes. + +```typescript +onStateChange( + callback: (state: State) => void, + emitCurrentState?: boolean +): Subscription +``` + +**Parameters:** + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `callback` | `(state: State) => void` | -- | Called whenever Bluetooth state changes. | +| `emitCurrentState` | `boolean` | `false` | If `true`, immediately emits the current state to the callback. | + +**Returns:** `Subscription` -- call `.remove()` to stop listening. + +```typescript +const sub = manager.onStateChange(state => { + console.log('BLE state:', state); +}, true); + +// Later: +sub.remove(); +``` + +--- + +## Scanning + +### `startDeviceScan(serviceUuids, options, callback): void` + +Starts scanning for BLE peripherals. Only one scan can be active at a time -- calling this again stops the previous scan. + +```typescript +startDeviceScan( + serviceUuids: string[] | null, + options: ScanOptions | null, + callback: (error: BleError | null, scannedDevice: ScanResult | null) => void +): void +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `serviceUuids` | `string[] \| null` | Filter by advertised service UUIDs. Pass `null` for all devices. | +| `options` | `ScanOptions \| null` | Platform-specific scan options. | +| `callback` | `function` | Called for each discovered device (may fire multiple times for the same device). | + +**Platform notes:** +- **Android:** `scanMode` controls scan aggressiveness. Use `legacyScan: false` to see BLE 5.0 extended advertisements. +- **iOS:** `allowDuplicates` controls whether the same device triggers the callback repeatedly. +- **iOS background:** You MUST specify `serviceUuids` -- passing `null` returns zero results when backgrounded. +- **Android throttling:** Android limits scan starts to ~5 per 30 seconds. Exceeding this silently returns zero results. + +```typescript +manager.startDeviceScan( + ['180a'], // Only devices advertising Device Information service + { scanMode: 2, legacyScan: false }, + (error, device) => { + if (error) { + console.error(error); + return; + } + console.log('Found:', device?.name, device?.id); + } +); +``` + +Scan results are batched by default (every 100ms, configurable via `scanBatchIntervalMs` in the constructor). This prevents the JS thread from being overwhelmed during a burst of advertisements. + +--- + +### `stopDeviceScan(): Promise` + +Stops the current scan. + +```typescript +await manager.stopDeviceScan(); +``` + +--- + +## Connection + +### `connectToDevice(deviceId, options?): Promise` + +Connects to a peripheral. + +```typescript +connectToDevice( + deviceId: string, + options?: ConnectOptions | null +): Promise +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `deviceId` | `string` | Device identifier (from scan results). | +| `options` | `ConnectOptions \| null` | Connection options. | + +**Returns:** `Promise` -- the connected device info. + +**Platform notes:** +- **Android:** Automatically requests MTU 517 after connecting (the #1 source of silent data truncation in v3 -- you'd be amazed how many "my data is corrupted" bugs were just MTU 23). On Android 14+, the system already negotiates MTU 517 on first connection, so the library skips the explicit request to avoid disconnects on some peripherals. +- **Android `autoConnect`:** When `true`, connects opportunistically in the background (slower but persists across Bluetooth toggles). When `false` (default), connects directly and fast. +- **Retries:** Only retryable errors trigger retry (GATT 133, connection timeout). Permission denied, device not found, and user-cancelled errors do not retry. + +```typescript +const device = await manager.connectToDevice(deviceId, { + timeout: 10000, + retries: 3, + retryDelay: 1000, + requestMtu: 512, +}); +``` + +--- + +### `cancelDeviceConnection(deviceId): Promise` + +Disconnects from a peripheral. + +```typescript +const device = await manager.cancelDeviceConnection(deviceId); +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `deviceId` | `string` | Device to disconnect from. | + +--- + +### `isDeviceConnected(deviceId): Promise` + +Checks if a device is currently connected. + +```typescript +const connected = await manager.isDeviceConnected(deviceId); +``` + +--- + +### `onDeviceDisconnected(deviceId, callback): Subscription` + +Subscribes to disconnection events for a specific device. + +```typescript +onDeviceDisconnected( + deviceId: string, + callback: (error: BleError | null, device: ConnectionStateEvent | null) => void +): Subscription +``` + +If the disconnection was caused by an error (e.g., GATT failure, connection lost), the `error` parameter will contain a `BleError` with diagnostic info. If the disconnection was intentional (you called `cancelDeviceConnection`), `error` will be `null`. + +```typescript +const sub = manager.onDeviceDisconnected(deviceId, (error, event) => { + if (error) { + console.error('Unexpected disconnect:', error.message, 'GATT status:', error.gattStatus); + } else { + console.log('Clean disconnect'); + } +}); +``` + +--- + +## Discovery + +### `discoverAllServicesAndCharacteristics(deviceId, transactionId?): Promise` + +Discovers all services and characteristics on a connected device. Must be called once after connecting before you can read, write, or monitor characteristics. + +```typescript +discoverAllServicesAndCharacteristics( + deviceId: string, + transactionId?: string | null +): Promise +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `deviceId` | `string` | Connected device ID. | +| `transactionId` | `string \| null` | Optional transaction ID for cancellation. | + +```typescript +await manager.discoverAllServicesAndCharacteristics(deviceId); +``` + +--- + +## Read / Write + +### `readCharacteristicForDevice(deviceId, serviceUuid, characteristicUuid, transactionId?): Promise` + +Reads the current value of a characteristic. The device must be connected and services discovered. + +```typescript +readCharacteristicForDevice( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + transactionId?: string | null +): Promise +``` + +**Returns:** `Promise` -- `value` is Base64-encoded. + +```typescript +const result = await manager.readCharacteristicForDevice( + deviceId, + '0000180a-0000-1000-8000-00805f9b34fb', + '00002a29-0000-1000-8000-00805f9b34fb' +); +const decoded = atob(result.value ?? ''); +``` + +--- + +### `writeCharacteristicForDevice(deviceId, serviceUuid, characteristicUuid, value, withResponse, transactionId?): Promise` + +Writes a value to a characteristic. + +```typescript +writeCharacteristicForDevice( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + value: string, // Base64-encoded + withResponse: boolean, + transactionId?: string | null +): Promise +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `value` | `string` | Base64-encoded data to write. | +| `withResponse` | `boolean` | `true` = write with ATT acknowledgment (slower, reliable). `false` = fire-and-forget (faster, no confirmation). | + +**Platform notes:** +- **`withResponse: true`**: The promise resolves when the peripheral acknowledges receipt. +- **`withResponse: false`**: The promise resolves when the data is queued for transmission. On iOS, the library checks `canSendWriteWithoutResponse` and waits for flow control if the buffer is full. + +```typescript +const base64Value = btoa('hello'); +await manager.writeCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + base64Value, + true // with response +); +``` + +--- + +## Monitoring + +### `monitorCharacteristicForDevice(deviceId, serviceUuid, characteristicUuid, listener, options?): Subscription` + +Subscribes to characteristic notifications or indications. This is how you receive streaming data from a peripheral. + +```typescript +monitorCharacteristicForDevice( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + listener: (error: BleError | null, characteristic: CharacteristicValueEvent | null) => void, + options?: MonitorOptions +): Subscription +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `listener` | `function` | Called for each notification/indication. | +| `options.transactionId` | `string` | Custom transaction ID for cancellation. Auto-generated if not provided. | +| `options.batchInterval` | `number` | Batch interval in ms. `0` = immediate delivery. Default `0`. | +| `options.subscriptionType` | `'notify' \| 'indicate' \| null` | Subscription type. `null` = auto-detect from characteristic properties (prefers notify). | + +**Returns:** `Subscription` -- call `.remove()` to stop monitoring. This cleans up both the JavaScript listener and the native notification registration. + +```typescript +const sub = manager.monitorCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + (error, event) => { + if (error) return console.error(error); + console.log('Value:', event?.value); + }, + { subscriptionType: 'indicate', batchInterval: 50 } +); + +// Later: +sub.remove(); +``` + +--- + +## MTU + +### `requestMTUForDevice(deviceId, mtu, transactionId?): Promise` + +Requests a specific MTU (Maximum Transmission Unit) size. + +```typescript +requestMTUForDevice( + deviceId: string, + mtu: number, + transactionId?: string | null +): Promise +``` + +**Platform notes:** +- **Android:** The actual MTU is negotiated -- you may get less than you asked for. Android 14+ automatically negotiates MTU 517 on first connection, so calling this again may cause disconnects on some peripherals. +- **iOS:** MTU is negotiated automatically by CoreBluetooth. Calling this is a no-op. Use `getMtu()` to read the current value. + +```typescript +const device = await manager.requestMTUForDevice(deviceId, 512); +console.log('Negotiated MTU:', device.mtu); +``` + +--- + +### `getMtu(deviceId): Promise` + +Returns the current MTU for a connected device. + +```typescript +const mtu = await manager.getMtu(deviceId); +``` + +--- + +## PHY + +BLE 5.0 Physical Layer selection. Higher PHY rates (2M) give faster throughput; coded PHY gives longer range. + +### `requestPhy(deviceId, txPhy, rxPhy): Promise` + +Requests a specific PHY for transmit and receive. + +```typescript +requestPhy( + deviceId: string, + txPhy: number, + rxPhy: number +): Promise +``` + +**PHY values:** `1` = LE 1M (default), `2` = LE 2M, `3` = LE Coded. + +**Platform notes:** +- **Android only.** iOS does not expose PHY selection -- CoreBluetooth handles it automatically, and you'll just have to trust that it's making good choices. +- The peripheral must also support the requested PHY. If it doesn't, the controller falls back to LE 1M. + +```typescript +const phy = await manager.requestPhy(deviceId, 2, 2); // Request 2M PHY +console.log('TX PHY:', phy.txPhy, 'RX PHY:', phy.rxPhy); +``` + +--- + +### `readPhy(deviceId): Promise` + +Reads the current PHY for a connected device. + +```typescript +const phy = await manager.readPhy(deviceId); +``` + +**Platform notes:** Android only. + +--- + +## Connection Priority + +### `requestConnectionPriority(deviceId, priority): Promise` + +Requests a connection priority level. + +```typescript +requestConnectionPriority( + deviceId: string, + priority: ConnectionPriority +): Promise +``` + +**Platform notes:** +- **Android only.** This is a coarse hint to the Bluetooth controller -- `High` means lower latency (connection interval ~11ms), `LowPower` means less radio usage (interval ~100ms), `Balanced` is in between. +- **iOS:** No-op. iOS handles connection parameters automatically, because Apple knows best. Fine-grained interval/latency/timeout can only be set by the peripheral's firmware, not from the app. + +```typescript +import { ConnectionPriority } from 'react-native-ble-plx'; +await manager.requestConnectionPriority(deviceId, ConnectionPriority.High); +``` + +--- + +## L2CAP + +L2CAP (Logical Link Control and Adaptation Protocol) channels provide a stream-oriented data channel between devices, bypassing the GATT overhead. + +### `openL2CAPChannel(deviceId, psm): Promise` + +Opens an L2CAP channel to a connected device. + +```typescript +openL2CAPChannel( + deviceId: string, + psm: number +): Promise +``` + +**Platform notes:** +- **iOS only.** Android L2CAP support is limited and not exposed in this library. +- PSM (Protocol/Service Multiplexer) must be a dynamic value read from a GATT characteristic on the peripheral. +- L2CAP channels do NOT wake suspended iOS apps -- only GATT notifications do. + +```typescript +const channel = await manager.openL2CAPChannel(deviceId, 0x0080); +console.log('Channel ID:', channel.channelId); +``` + +--- + +### `writeL2CAPChannel(channelId, data): Promise` + +Writes data to an open L2CAP channel. + +```typescript +await manager.writeL2CAPChannel(channelId, btoa('payload')); +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `channelId` | `number` | Channel ID from `openL2CAPChannel`. | +| `data` | `string` | Base64-encoded data to send. | + +--- + +### `closeL2CAPChannel(channelId): Promise` + +Closes an L2CAP channel. + +```typescript +await manager.closeL2CAPChannel(channelId); +``` + +--- + +### `monitorL2CAPChannel(channelId, listener): Subscription` + +Subscribes to incoming data and close events on an open L2CAP channel. The listener receives data frames as Base64-encoded strings. + +When the channel closes, the listener fires one final time and the subscription auto-cleans itself (removing both native listeners and the manager's tracked subscription): + +- **Clean close:** the listener receives `(null, null)` -- both error and data are null, indicating graceful shutdown. +- **Error close:** the listener receives `(BleError, null)` -- the error describes what went wrong. + +In both cases, you do **not** need to call `.remove()` on the subscription after an auto-close -- it has already been cleaned up. + +```typescript +monitorL2CAPChannel( + channelId: number, + listener: (error: BleError | null, data: { channelId: number; data: string } | null) => void +): Subscription +``` + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `channelId` | `number` | Channel ID from `openL2CAPChannel`. | +| `listener` | `function` | Called for each incoming data frame. On channel close, called once with `(BleError, null)` for error close or `(null, null)` for clean close. | + +**Returns:** `Subscription` -- call `.remove()` to stop listening before the channel closes. Removing the subscription does **not** close the underlying L2CAP channel; call `closeL2CAPChannel` separately. If the channel has already closed, the subscription is already removed and calling `.remove()` is a safe no-op. + +```typescript +const channel = await manager.openL2CAPChannel(deviceId, psm); + +const sub = manager.monitorL2CAPChannel(channel.channelId, (error, data) => { + if (error) { + console.error('L2CAP error close:', error.message); + return; + } + if (data == null) { + console.log('L2CAP channel closed cleanly'); + return; + } + console.log('Received:', data.data); // Base64-encoded +}); + +// When done, close the channel (subscription auto-removes on close): +await manager.closeL2CAPChannel(channel.channelId); +``` + +--- + +## Bonding + +### `getBondedDevices(): Promise` + +Returns the list of bonded (paired) BLE devices. + +```typescript +const bonded = await manager.getBondedDevices(); +bonded.forEach(d => console.log(d.name, d.id)); +``` + +**Platform notes:** +- **Android:** Returns devices from `BluetoothAdapter.getBondedDevices()`. Note: bond state only indicates that pairing info exists -- it does NOT verify the link is currently encrypted. +- **iOS:** Returns an empty array. iOS has no equivalent API. + +--- + +## Authorization + +### `getAuthorizationStatus(): Promise` + +Returns the current Bluetooth authorization status. + +```typescript +const status = await manager.getAuthorizationStatus(); +// 'NotDetermined' | 'Restricted' | 'Denied' | 'Authorized' +``` + +**Platform notes:** +- **iOS:** Returns the actual `CBManager.authorization` value. +- **Android:** Always returns `'Authorized'` (use runtime permission checks instead). + +--- + +## Events + +### `onRestoreState(callback): Subscription` + +Subscribes to iOS state restoration events. Called when the system relaunches your app due to a BLE event. + +```typescript +const sub = manager.onRestoreState(event => { + console.log('Restored devices:', event.devices); +}); +``` + +**Platform notes:** iOS only. Requires `createClient()` to be called with a `restoreStateIdentifier`. + +--- + +### `onBondStateChange(callback): Subscription` + +Subscribes to bond state changes. + +```typescript +const sub = manager.onBondStateChange(event => { + console.log('Device', event.deviceId, 'bond state:', event.bondState); +}); +``` + +**Platform notes:** Android only. `bondState` is one of `'none'`, `'bonding'`, `'bonded'`. + +--- + +### `onConnectionEvent(callback): Subscription` + +Subscribes to connection events. + +```typescript +const sub = manager.onConnectionEvent(event => { + console.log('Device', event.deviceId, 'connection:', event.connectionState); +}); +``` + +**Platform notes:** iOS 13+. + +--- + +## Cancellation + +### `cancelTransaction(transactionId): Promise` + +Cancels a pending BLE operation by transaction ID. + +```typescript +await manager.cancelTransaction('my-read-tx'); +``` + +Any operation that accepts an optional `transactionId` parameter can be cancelled this way. If the operation has already completed, this is a no-op. + +--- + +## Error Codes + +All errors thrown by the library are instances of `BleError` with a `code` property from `BleErrorCode`: + +| Code | Name | Value | Description | +|------|------|-------|-------------| +| Connection | `DeviceNotFound` | `0` | Device not found or out of range | +| | `DeviceDisconnected` | `1` | Device disconnected unexpectedly | +| | `ConnectionFailed` | `2` | Connection attempt failed | +| | `ConnectionTimeout` | `3` | Connection timed out | +| Operations | `OperationCancelled` | `100` | Operation cancelled via `cancelTransaction` | +| | `OperationTimeout` | `101` | Operation timed out | +| | `OperationNotSupported` | `102` | Operation not supported on this platform | +| | `OperationInProgress` | `103` | Another operation is already in progress | +| GATT | `CharacteristicNotFound` | `200` | Characteristic UUID not found | +| | `ServiceNotFound` | `201` | Service UUID not found | +| | `DescriptorNotFound` | `202` | Descriptor not found | +| | `CharacteristicWriteFailed` | `203` | Write operation failed | +| | `CharacteristicReadFailed` | `204` | Read operation failed | +| | `MTUNegotiationFailed` | `205` | MTU negotiation failed | +| Permissions | `BluetoothUnauthorized` | `300` | Bluetooth not authorized | +| | `BluetoothPoweredOff` | `301` | Bluetooth is powered off | +| | `LocationPermissionDenied` | `302` | Location permission denied | +| | `ScanPermissionDenied` | `303` | Scan permission denied | +| | `ConnectPermissionDenied` | `304` | Connect permission denied | +| Manager | `ManagerNotInitialized` | `400` | `createClient()` not called | +| | `ManagerDestroyed` | `401` | `destroyClient()` already called | +| Bonding | `BondingFailed` | `500` | Bonding/pairing failed | +| | `BondLost` | `501` | Bond information lost | +| | `PairingRejected` | `502` | Pairing rejected by user or peripheral | +| L2CAP | `L2CAPChannelFailed` | `600` | L2CAP channel open failed | +| | `L2CAPChannelClosed` | `601` | L2CAP channel closed unexpectedly | +| PHY | `PhyNegotiationFailed` | `700` | PHY negotiation failed | +| Scan | `ScanFailed` | `800` | Scan start failed | +| | `ScanThrottled` | `801` | Too many scan starts (Android throttle) | +| Other | `UnknownError` | `999` | Catch-all for unrecognized errors | + +### `BleError` Properties + +Every `BleError` instance includes: + +| Property | Type | Description | +|----------|------|-------------| +| `code` | `BleErrorCode` | Unified error code (see table above) | +| `message` | `string` | Human-readable description | +| `isRetryable` | `boolean` | Whether the operation can be retried | +| `deviceId` | `string \| undefined` | Device that caused the error | +| `serviceUUID` | `string \| undefined` | Relevant service UUID | +| `characteristicUUID` | `string \| undefined` | Relevant characteristic UUID | +| `operation` | `string \| undefined` | Operation that failed (e.g., `'read'`, `'write'`, `'connect'`) | +| `platform` | `'android' \| 'ios'` | Which platform threw the error | +| `nativeDomain` | `string \| undefined` | Native error domain (e.g., `'CBError'`) | +| `nativeCode` | `number \| undefined` | Raw platform error code | +| `gattStatus` | `number \| undefined` | Android GATT status (0=success, 133=common failure) | +| `attErrorCode` | `number \| undefined` | ATT protocol error code | diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 2d5e4277d..24415b7ba 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,193 +1,395 @@ -

- react-native-ble-plx -

+# Getting Started with react-native-ble-plx v4 -This guide is an introduction to BLE stack and APIs exported by this library. For further information you can refer to +This guide covers everything from installation to your first successful BLE read. If you've used v3 before, the API will feel familiar -- but the internals are completely different. If you're new to BLE on mobile, buckle up. -- tutorials and API reference available in this documentation, -- [GitHub wiki](https://github.com/dotintent/react-native-ble-plx/wiki), -- example app available in the repository. +## Prerequisites -### Install and prepare package +- **React Native 0.82+** (New Architecture / TurboModules required) +- **Expo SDK 55+** (if using Expo) +- **iOS 15+** deployment target +- **Android API 23+** (minSdk) +- **A physical device** -- BLE does not work in the iOS Simulator or Android Emulator (unless you enjoy staring at "PoweredOff" state forever) -In the case of Expo, you will need to prepare a plugin config, detailed information can be found here: https://github.com/dotintent/react-native-ble-plx?tab=readme-ov-file#expo-sdk-43 -In the case of react native CLI you need to configure two environments: +## Installation -- [iOS](https://github.com/dotintent/react-native-ble-plx?tab=readme-ov-file#ios-example-setup) -- [Android](https://github.com/dotintent/react-native-ble-plx?tab=readme-ov-file#android-example-setup) +### Expo (Recommended) -### Creating BLE Manager +If you're using Expo (and you should be), this is the easy part. -First step is to create BleManager instance which is an entry point to all available APIs. Make sure to create it after application started its execution. We can keep it as a static reference by either creating our own abstraction (ex.1) or by simply creating a new instance (ex.2). +```bash +npx expo install react-native-ble-plx +``` + +Add the config plugin to your `app.json` or `app.config.js`: + +```json +{ + "expo": { + "plugins": ["react-native-ble-plx"] + } +} +``` -#### Ex.1 +That's it. The plugin automatically handles: -```ts -import { BleManager } from 'react-native-ble-plx' +- **iOS:** Adds `NSBluetoothAlwaysUsageDescription` to your Info.plist +- **Android:** Adds `BLUETOOTH`, `BLUETOOTH_ADMIN`, `BLUETOOTH_CONNECT`, and `BLUETOOTH_SCAN` permissions to your manifest, with `neverForLocation` set by default -// create your own singleton class -class BLEServiceInstance { - manager: BleManager +Build and run on a physical device: - constructor() { - this.manager = new BleManager() +```bash +npx expo run:ios --device +# or +npx expo run:android +``` + +This library requires native code, so it **does not work with Expo Go**. You need a [development build](https://docs.expo.dev/develop/development-builds/introduction/). Yes, this means goodbye to the QR-code-and-pray workflow. BLE is a native API; there's no JavaScript polyfill for radio hardware. + +#### Config Plugin Options + +Need background BLE or custom permission strings? Pass options to the plugin: + +```json +{ + "expo": { + "plugins": [ + [ + "react-native-ble-plx", + { + "isBackgroundEnabled": true, + "modes": ["central"], + "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices" + } + ] + ] } } +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `isBackgroundEnabled` | boolean | `false` | Add `bluetooth-central` to iOS background modes | +| `modes` | string[] | `[]` | iOS `UIBackgroundModes`: `"peripheral"`, `"central"` | +| `bluetoothAlwaysPermission` | string \| false | `'Allow $(PRODUCT_NAME) to connect to bluetooth devices'` | iOS `NSBluetoothAlwaysUsageDescription`. Set to `false` to skip. | +| `neverForLocation` | boolean | `true` | If true, adds `neverForLocation` flag to Android `BLUETOOTH_SCAN`. Only set to `false` if you're deriving physical location from BLE scans. | + +### Bare React Native + +If you're going bare... you chose this life. + +```bash +npm install react-native-ble-plx +# or +yarn add react-native-ble-plx +``` + +Then install pods: + +```bash +cd ios && pod install && cd .. +``` + +You'll also need to manually configure iOS and Android permissions -- see the Platform Setup section below. + +## Platform Setup + +### Handled Automatically by Expo Plugin + +If you're using the Expo config plugin, skip this entire section. The plugin configures Info.plist permissions, Android manifest permissions, and the `neverForLocation` flag for you. Go directly to [Runtime Permissions](#runtime-permissions-all-projects). + +### iOS (Bare RN Only) + +#### Info.plist + +Add the Bluetooth usage description. iOS requires this since iOS 13 -- without it, your app will crash on launch when it tries to access Bluetooth. + +```xml +NSBluetoothAlwaysUsageDescription +This app uses Bluetooth to communicate with BLE devices. +``` + +#### Background Mode (Optional) + +If your app needs to maintain BLE connections while backgrounded: + +1. In Xcode, select your app target +2. Go to **Signing & Capabilities** +3. Add **Background Modes** +4. Check **Uses Bluetooth LE accessories** + +Or in your `Info.plist`: + +```xml +UIBackgroundModes + + bluetooth-central + +``` + +#### State Restoration (Optional) + +If you want iOS to relaunch your app when a BLE event occurs after the system killed it: + +```typescript +const manager = new BleManager(); +await manager.createClient('my-restore-identifier'); + +manager.onRestoreState(event => { + // event.devices contains previously connected peripherals + console.log('Restored devices:', event.devices); +}); +``` + +State restoration only works for system-terminated apps. If the user force-quits from the app switcher, restoration is disabled. + +### Android (Bare RN Only) + +#### AndroidManifest.xml + +Add the required permissions: -export const BLEService = new BLEServiceInstance() +```xml + + + + + + + + + + + + + + + + + + ``` -#### Ex.2 +#### The `neverForLocation` Option (Android 12+) -```ts -import { BleManager } from 'react-native-ble-plx' +If your app never derives physical location from BLE scan results, you can skip the location permission: -export const manager = new BleManager() +```xml + + ``` -When you don't need any BLE functionality you can destroy created instance by calling `manager.destroy()` function. You can then recreate `BleManager` later on. +With this flag, you only need `BLUETOOTH_SCAN` and `BLUETOOTH_CONNECT` at runtime on Android 12+. No location permission dance. + +**Warning:** If your app uses BLE beacons for indoor positioning or otherwise derives location from scan data, do NOT set this flag. Google will reject your app. -### Ask for permissions +## Runtime Permissions (All Projects) -Check if you requested following permissions +This section applies to both Expo and bare React Native. The native manifest/plist entries declare what your app *can* ask for. You still need to ask the user at runtime. -- PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION -- PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN (necessary for api 31+ ) -- PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT (necessary for api 31+ ) +Android's BLE permission model makes tax law look simple. Here's the full dance: -eg. +```typescript +import { Platform, PermissionsAndroid } from 'react-native'; -```js -requestBluetoothPermission = async () => { +async function requestBlePermissions(): Promise { if (Platform.OS === 'ios') { - return true + // iOS handles permissions via Info.plist -- nothing to request at runtime + return true; } - if (Platform.OS === 'android' && PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) { - const apiLevel = parseInt(Platform.Version.toString(), 10) + + if (Platform.OS === 'android') { + const apiLevel = Number(Platform.Version); if (apiLevel < 31) { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) - return granted === PermissionsAndroid.RESULTS.GRANTED - } - if (PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN && PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT) { - const result = await PermissionsAndroid.requestMultiple([ - PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, - PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, + // Android 11 and below: need location permission + const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION - ]) - - return ( - result['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED && - result['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED && - result['android.permission.ACCESS_FINE_LOCATION'] === PermissionsAndroid.RESULTS.GRANTED - ) + ); + return granted === PermissionsAndroid.RESULTS.GRANTED; } - } - this.showErrorToast('Permission have not been granted') + // Android 12+: need BLUETOOTH_SCAN and BLUETOOTH_CONNECT + const result = await PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, + PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, + // Include location if you did NOT set neverForLocation + PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, + ]); + + return ( + result['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED && + result['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED && + result['android.permission.ACCESS_FINE_LOCATION'] === PermissionsAndroid.RESULTS.GRANTED + ); + } - return false + return false; } ``` -With `neverForLocation` flag active, you can remove `ACCESS_FINE_LOCATION` permissions ask e.g.: +If you set `neverForLocation: true` (the default in the Expo plugin), you can drop `ACCESS_FINE_LOCATION` from the Android 12+ branch. One less dialog for the user, one less reason for them to hit "Deny." -```js -const result = await PermissionsAndroid.requestMultiple([ - PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, - PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT -]) +## Your First Scan, Connect, and Read -return ( - result['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED && - result['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED -) -``` +Here's a complete working example. This is the whole flow: check state, scan, connect, discover, read. -### Waiting for Powered On state +```typescript +import { BleManager, State, BleError, ScanResult } from 'react-native-ble-plx'; -When iOS application launches BLE stack is not immediately available and we need to check its status. -To detect current state and following state changes we can use `onStateChange()` function: +const manager = new BleManager(); -```js -React.useEffect(() => { - const subscription = manager.onStateChange(state => { - if (state === 'PoweredOn') { - scanAndConnect() - subscription.remove() - } - }, true) - return () => subscription.remove() -}, [manager]) -``` +async function main() { + // 1. Initialize the native BLE client + await manager.createClient(); -### Scanning devices + // 2. Wait for Bluetooth to be powered on + const state = await manager.state(); + if (state !== State.PoweredOn) { + console.log('Bluetooth is not on. Current state:', state); + await waitForPoweredOn(); + } -Devices needs to be scanned first to be able to connect to them. There is a simple function -which allows only one callback to be registered to handle detected devices: + // 3. Request permissions (Android) + const hasPermission = await requestBlePermissions(); + if (!hasPermission) { + console.error('BLE permissions not granted'); + return; + } -```js -function scanAndConnect() { - manager.startDeviceScan(null, null, (error, device) => { - if (error) { - // Handle error (scanning will be stopped automatically) - return - } + // 4. Scan for a specific device + console.log('Scanning...'); + const device = await scanForDevice('MyPeripheral'); + console.log('Found device:', device.id, device.name); + + // 5. Connect + const connectedDevice = await manager.connectToDevice(device.id, { + timeout: 10000, // 10 second timeout + retries: 2, // retry twice on failure + }); + console.log('Connected! MTU:', connectedDevice.mtu); + + // 6. Discover services and characteristics + await manager.discoverAllServicesAndCharacteristics(device.id); + + // 7. Read a characteristic + const characteristic = await manager.readCharacteristicForDevice( + device.id, + '0000180a-0000-1000-8000-00805f9b34fb', // Device Information service + '00002a29-0000-1000-8000-00805f9b34fb', // Manufacturer Name characteristic + ); + console.log('Manufacturer:', atob(characteristic.value ?? '')); + + // 8. Clean up when done + await manager.cancelDeviceConnection(device.id); + await manager.destroyClient(); +} - // Check if it is a device you are looking for based on advertisement data - // or other criteria. - if (device.name === 'TI BLE Sensor Tag' || device.name === 'SensorTag') { - // Stop scanning as it's not necessary if you are scanning for one device. - manager.stopDeviceScan() +function waitForPoweredOn(): Promise { + return new Promise(resolve => { + const sub = manager.onStateChange(state => { + if (state === State.PoweredOn) { + sub.remove(); + resolve(); + } + }, true); // emitCurrentState prevents race condition + }); +} - // Proceed with connection. - } - }) +function scanForDevice(name: string): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(async () => { + await manager.stopDeviceScan(); + reject(new Error('Scan timed out')); + }, 15000); + + manager.startDeviceScan(null, null, async (error, device) => { + if (error) { + clearTimeout(timeout); + reject(error); + return; + } + if (device?.name === name) { + clearTimeout(timeout); + await manager.stopDeviceScan(); + resolve(device); + } + }); + }); } ``` -It is worth to note that scanning function may emit one device multiple times. However -when device is connected it won't broadcast and needs to be disconnected from central -to be scanned again. Only one scanning listener can be registered. - -#### Bluetooth 5 Advertisements in Android - -To see devices that use Bluetooth 5 Advertising Extension you have to set the `legacyScan` variable to `false` in {@link #scanoptions|Scan options} when you are starting {@link #blemanagerstartdevicescan|BleManager.startDeviceScan()}, - -### Connecting and discovering services and characteristics - -Once device is scanned it is in disconnected state. We need to connect to it and discover -all services and characteristics it contains. Services may be understood -as containers grouping characteristics based on their meaning. Characteristic is a -container for a value which can be read, written and monitored based on available -capabilities. For example connection may look like this: - -```js -device - .connect() - .then(device => { - return device.discoverAllServicesAndCharacteristics() - }) - .then(device => { - // Do work on device with services and characteristics - }) - .catch(error => { - // Handle errors - }) +## Monitoring Characteristic Notifications + +For streaming data (notifications or indications): + +```typescript +const subscription = manager.monitorCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + (error, characteristic) => { + if (error) { + console.error('Monitor error:', error); + return; + } + console.log('New value:', characteristic?.value); + }, + { + subscriptionType: 'notify', // or 'indicate', or null for auto-detect + batchInterval: 0, // 0 = immediate delivery, >0 = batch in ms + } +); + +// When you're done listening: +subscription.remove(); ``` -Discovery of services and characteristics is required to be executed once per connection\*. -It can be a long process depending on number of characteristics and services available. +The subscription `.remove()` call is important -- it cleans up both the JavaScript listener and the native notification registration. In v3, this was broken. In v4, it works properly. + +## Common Gotchas + +### 1. BLE Doesn't Work in Simulators + +The iOS Simulator and Android Emulator don't support Bluetooth. You need a physical device. There is no workaround. If you see `State.PoweredOff` or `State.Unsupported`, check if you're running on a real device. + +### 2. Android 12+ Permission Changes -\* Extremely rarely, when peripheral's service/characteristic set can change during a connection -an additional service discovery may be needed. +Android 12 (API 31) introduced `BLUETOOTH_SCAN` and `BLUETOOTH_CONNECT` as separate runtime permissions. The old `BLUETOOTH` and `BLUETOOTH_ADMIN` permissions still need to be in the manifest (with `maxSdkVersion="30"`) for backward compatibility, but the runtime request is different. See the permission section above. -### Read, write and monitor values +### 3. iOS Bluetooth State on Launch -After successful discovery of services you can call +When your app launches, iOS reports the Bluetooth state as `Unknown` for a brief moment before settling on the actual state. Always use `onStateChange` with `emitCurrentState: true` or poll `state()` and wait for `PoweredOn` before attempting any BLE operations. + +### 4. Scanning Finds the Same Device Multiple Times + +This is normal. A BLE peripheral broadcasts advertisements periodically, and each broadcast triggers your scan callback. Filter duplicates by device ID on the JS side, or use `allowDuplicates: false` on iOS. + +### 5. Must Discover Before Read/Write + +You cannot read or write characteristics until you've called `discoverAllServicesAndCharacteristics()`. This is a fundamental BLE requirement, not a library limitation. Discovery only needs to happen once per connection. + +### 6. Values Are Base64-Encoded + +All characteristic values are Base64-encoded strings. Use `atob()` to decode or `btoa()` to encode. For binary data, decode the Base64 string to a byte array: + +```typescript +function base64ToBytes(base64: string): Uint8Array { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} +``` -- {@link #blemanagerreadcharacteristicfordevice|BleManager.readCharacteristicForDevice()}, -- {@link #blemanagerwritecharacteristicwithresponsefordevice|BleManager.writeCharacteristicWithResponseForDevice()}, -- {@link #blemanagermonitorcharacteristicfordevice|BleManager.monitorCharacteristicForDevice()} +## Next Steps -and other functions which are described in detail in documentation. You can also check our _example app_ which is available in the repository. +- **[API Reference](API.md)** -- full documentation for every `BleManager` method +- **[Troubleshooting](TROUBLESHOOTING.md)** -- when things go wrong (and they will) +- **[Migration from v3](MIGRATION_V3_TO_V4.md)** -- if you're upgrading an existing app diff --git a/docs/MIGRATION_V3_TO_V4.md b/docs/MIGRATION_V3_TO_V4.md new file mode 100644 index 000000000..31c2e92c2 --- /dev/null +++ b/docs/MIGRATION_V3_TO_V4.md @@ -0,0 +1,282 @@ +# Migrating from v3 to v4 + +v4 is a ground-up rewrite. The JavaScript API is intentionally similar to v3, so upgrading shouldn't require rewriting your entire BLE layer -- but there are real breaking changes that need attention. + +## The Short Version + +1. Update your React Native to 0.82+ (New Architecture required) +2. Update the package +3. Fix the 4-5 breaking changes listed below +4. Enjoy things actually working correctly + +## Requirements + +| Requirement | v3 | v4 | +|-------------|----|----| +| React Native | 0.60+ | **0.82+** | +| Architecture | Bridge (old) or New | **New Architecture only** | +| Expo SDK | 43+ | **55+** | +| iOS | 13+ | **15+** | +| Android API | 18+ | **23+** | + +If you're still on the Bridge architecture, you need to migrate to New Architecture first. React Native 0.82 removed the legacy architecture entirely, so this isn't optional. + +## Package Update + +```diff +- "react-native-ble-plx": "^3.x.x" ++ "react-native-ble-plx": "^4.0.0" +``` + +```bash +npm install react-native-ble-plx@latest +cd ios && pod install +``` + +## Breaking Changes + +### 1. `enable()` and `disable()` Are Gone + +These methods were broken on Android 12+ (the system throws a SecurityException) and were always a no-op on iOS. They've been removed. + +```typescript +// v3 +await manager.enable(); // Broken on Android 12+ +await manager.disable(); // Broken on Android 12+ + +// v4 -- redirect users to system settings +import { Linking, Platform } from 'react-native'; +if (Platform.OS === 'android') { + Linking.sendIntent('android.settings.BLUETOOTH_SETTINGS'); +} else { + Linking.openURL('App-Prefs:Bluetooth'); +} +``` + +### 2. Enums Are Now `const` Objects + +v3 used TypeScript enums, which had runtime type mismatch issues (the native module returned strings, but the enum compared as numbers). v4 uses `as const` objects, which are strings all the way down. + +```typescript +// v3 +import { State } from 'react-native-ble-plx'; +if (state === State.PoweredOn) { ... } // Worked, but types were wrong + +// v4 -- same usage, correct types +import { State } from 'react-native-ble-plx'; +if (state === State.PoweredOn) { ... } // Works and types are correct +``` + +The usage looks identical, but if you were doing numeric comparisons against enum values, those will break: + +```typescript +// v3 -- don't do this, but some people did +if (state === 5) { ... } // PoweredOn was enum value 5 + +// v4 -- this no longer works +if (state === 'PoweredOn') { ... } // Use the string value +``` + +Affected types: `State`, `LogLevel`, `ConnectionPriority`, `ConnectionState`. + +### 3. `BleManager` Is No Longer a Silent Singleton + +In v3.4.0, `BleManager` became a singleton that silently returned the same instance. In v4, each `new BleManager()` creates an independent instance. If you relied on the singleton behavior, create your own: + +```typescript +// v4 -- make your own singleton if you need one +let instance: BleManager | null = null; + +export function getBleManager(): BleManager { + if (!instance) { + instance = new BleManager(); + } + return instance; +} +``` + +### 4. `createClient()` Must Be Called Explicitly + +In v3, the native client was initialized automatically in the constructor. In v4, you must call `createClient()` before doing anything: + +```typescript +// v3 +const manager = new BleManager(); +manager.startDeviceScan(...); // Worked immediately + +// v4 +const manager = new BleManager(); +await manager.createClient(); // Required! +manager.startDeviceScan(...); +``` + +### 5. Monitor Subscription Cleanup Actually Works + +In v3, calling `.remove()` on a monitor subscription didn't fully clean up the JavaScript event listener (bugs #1308, #1299). In v4, `.remove()` properly tears down everything: the native notification registration, the JS event listener, and the transaction. + +This isn't really a breaking change -- it's a bug fix -- but if you wrote workarounds for the broken cleanup, you can remove them. + +## API Changes + +### Renamed / Changed Methods + +| v3 | v4 | Notes | +|----|----|----| +| `new BleManager()` (auto-init) | `new BleManager()` + `await createClient()` | Explicit initialization | +| `manager.enable()` | Removed | Use system settings | +| `manager.disable()` | Removed | Use system settings | +| `manager.setLogLevel()` | Removed | Log level not exposed in v4 TurboModule | + +### Changed Return Types + +Several methods that previously returned wrapper objects (`Device`, `Service`, `Characteristic`) now return plain data objects (`DeviceInfo`, `CharacteristicInfo`). The data is the same; the wrapper methods are gone. + +```typescript +// v3 +const device = await manager.connectToDevice(id); +await device.discoverAllServicesAndCharacteristics(); // Method on Device object +await device.readCharacteristicForService(serviceUuid, charUuid); + +// v4 -- use BleManager methods directly with device ID +const device = await manager.connectToDevice(id); +await manager.discoverAllServicesAndCharacteristics(device.id); +await manager.readCharacteristicForDevice(device.id, serviceUuid, charUuid); +``` + +The `Device`, `Service`, `Characteristic`, and `Descriptor` classes still exist as exports for backward compatibility but are thin wrappers. Prefer using `BleManager` methods directly. + +### New Parameters + +`connectToDevice` now accepts `retries` and `retryDelay`: + +```typescript +// v4 +await manager.connectToDevice(deviceId, { + timeout: 10000, + retries: 3, // New: retry on transient failures + retryDelay: 1000, // New: wait 1s between retries + requestMtu: 517, // New: auto-request MTU after connect +}); +``` + +`monitorCharacteristicForDevice` now accepts `batchInterval` and `subscriptionType`: + +```typescript +// v4 +manager.monitorCharacteristicForDevice( + deviceId, serviceUuid, charUuid, + listener, + { + batchInterval: 50, // New: batch notifications every 50ms + subscriptionType: 'indicate', // New: explicit indicate vs notify + } +); +``` + +## New Features in v4 + +### PHY Selection (Android) + +Request BLE 5.0 PHY modes for faster throughput or longer range: + +```typescript +await manager.requestPhy(deviceId, 2, 2); // LE 2M PHY +const phy = await manager.readPhy(deviceId); +``` + +### L2CAP Channels (iOS) + +Stream-oriented data transfer without GATT overhead: + +```typescript +const channel = await manager.openL2CAPChannel(deviceId, psm); +await manager.writeL2CAPChannel(channel.channelId, btoa('data')); +await manager.closeL2CAPChannel(channel.channelId); +``` + +### Connection Priority (Android) + +```typescript +import { ConnectionPriority } from 'react-native-ble-plx'; +await manager.requestConnectionPriority(deviceId, ConnectionPriority.High); +``` + +### Bond State Monitoring (Android) + +```typescript +const sub = manager.onBondStateChange(event => { + console.log(event.deviceId, event.bondState); // 'none' | 'bonding' | 'bonded' +}); +``` + +### Connection Events (iOS) + +```typescript +const sub = manager.onConnectionEvent(event => { + console.log(event.deviceId, event.connectionState); +}); +``` + +### Authorization Status (iOS) + +```typescript +const status = await manager.getAuthorizationStatus(); +// 'NotDetermined' | 'Restricted' | 'Denied' | 'Authorized' +``` + +### Event Batching + +High-frequency notifications no longer flood the JS thread: + +```typescript +// Batch notifications every 50ms +manager.monitorCharacteristicForDevice(deviceId, svc, char, listener, { + batchInterval: 50, +}); + +// Configure scan result batching +const manager = new BleManager({ scanBatchIntervalMs: 200 }); +``` + +### Unified Error Model + +Every error now includes rich diagnostic information: + +```typescript +try { + await manager.connectToDevice(deviceId); +} catch (e) { + if (e instanceof BleError) { + console.log('Code:', e.code); + console.log('Retryable:', e.isRetryable); + console.log('Platform:', e.platform); + console.log('GATT status:', e.gattStatus); // Android + console.log('ATT error:', e.attErrorCode); // iOS + console.log('Native code:', e.nativeCode); + } +} +``` + +## No More `NativeEventEmitter` Setup + +In v3, you might have seen code like this for handling native events: + +```typescript +// v3 -- manual NativeEventEmitter wiring +import { NativeEventEmitter, NativeModules } from 'react-native'; +const bleEmitter = new NativeEventEmitter(NativeModules.BleManager); +``` + +v4 uses TurboModule typed events. All event handling goes through `BleManager` methods (`onStateChange`, `onDeviceDisconnected`, `monitorCharacteristicForDevice`, etc.). No manual `NativeEventEmitter` setup needed. + +## Checklist + +- [ ] React Native updated to 0.82+ with New Architecture enabled +- [ ] Package updated to v4 +- [ ] `createClient()` called before any BLE operations +- [ ] `enable()` / `disable()` calls removed +- [ ] Enum numeric comparisons changed to string comparisons (if any) +- [ ] Singleton workarounds removed (if using v3.4+ singleton behavior) +- [ ] `NativeEventEmitter` manual setup removed +- [ ] Monitor cleanup workarounds removed +- [ ] Tested on physical devices (both platforms) diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 000000000..7f174f25c --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,278 @@ +# E2E Testing Infrastructure + +react-native-ble-plx v4 has automated end-to-end tests that run on physical hardware. BLE cannot be meaningfully tested in simulators, so we use two real phones connected via USB to a Mac, with maestro-runner driving the UI. + +## Overview + +The test setup works like this: +- **Device A** runs the example app (BLE scanner/central) +- **Device B** runs the BlePlxTest app (BLE peripheral/GATT server) +- **maestro-runner** automates the UI on Device A +- A shell script orchestrates the whole thing + +Each test flow connects to the BlePlxTest peripheral, performs BLE operations, and verifies results through the UI. + +## Hardware Setup + +| Device | Role | ID | +|--------|------|----| +| iPhone 15 Pro Max | Scanner or Peripheral | UDID: `00008130-000A34C12021401C` | +| Android phone | Scanner or Peripheral | Serial: `1A211FDF60055L` | + +Both devices connected via USB to a Mac. The test script can swap roles -- you can run the test suite with Android as the scanner and iPhone as the peripheral, or vice versa. + +## Test Apps (4 Total) + +| App | Platform | Package / Bundle ID | Purpose | +|-----|----------|-------------------|---------| +| Example App | Android | `com.bleplxexample` | Scanner under test | +| Example App | iOS | `com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-` | Scanner under test | +| BlePlxTest | Android | `com.bleplx.testperipheral` | GATT server peripheral | +| BlePlxTest | iOS | `com.bleplx.testperipheral.ios` | GATT server peripheral | + +The BlePlxTest peripheral exposes a known GATT service with these characteristics: +- **Read Counter** -- returns an incrementing integer on each read +- **Write Echo** -- write a value, read it back +- **Notify Stream** -- emits notifications at regular intervals +- **Indicate Stream** -- emits indications at regular intervals +- **MTU Test** -- returns the negotiated MTU value +- **Write No Response** -- accepts write-without-response, echoes via read +- **L2CAP** -- exposes a PSM for L2CAP channel testing + +## Prerequisites + +### Software + +- **maestro-runner** -- `~/.maestro-runner/bin/maestro-runner` + ```bash + curl -fsSL https://open.devicelab.dev/install/maestro-runner | bash + ``` +- **pymobiledevice3** -- for iOS device communication + ```bash + pip3 install pymobiledevice3 + ``` +- **Xcode** -- for iOS builds and `xcrun devicectl` +- **Android Studio** -- for `adb` and Gradle +- **JAVA_HOME** set to Android Studio's JBR (yes, you need a specific Java and yes, it matters): + ```bash + export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" + ``` + +### iOS Tunnel (Required for iOS Tests) + +Start the pymobiledevice3 tunnel in a separate terminal before running iOS tests: + +```bash +sudo pymobiledevice3 remote tunneld -d +``` + +This stays running for the duration of your test session. + +### BLE Permissions (One-Time Setup) + +Pre-grant permissions so the test flows don't get stuck on system dialogs: + +**Android:** +```bash +adb shell pm grant com.bleplxexample android.permission.BLUETOOTH_SCAN +adb shell pm grant com.bleplxexample android.permission.BLUETOOTH_CONNECT +adb shell pm grant com.bleplxexample android.permission.ACCESS_FINE_LOCATION +``` + +**iOS:** Launch the example app manually once and accept all permission prompts. + +### Apple Team ID (iOS) + +maestro-runner builds WebDriverAgent on the first iOS run. It needs the Apple Developer Team ID (`2974F4A5QH`). After the first build, trust the WDA app on the iPhone: **Settings > General > VPN & Device Management**. + +## Running Tests + +```bash +# Android as scanner, iPhone as BlePlxTest peripheral +./integration-tests/hardware/maestro/run-e2e.sh android + +# iPhone as scanner, Android as BlePlxTest peripheral +./integration-tests/hardware/maestro/run-e2e.sh ios + +# Run a single test (e.g., test 03) +./integration-tests/hardware/maestro/run-e2e.sh android 03 +``` + +### What the Script Does + +1. Kills BLE apps on both devices (clean slate) +2. Launches BlePlxTest peripheral on the opposite device +3. Waits 5 seconds for the peripheral to start advertising +4. Runs maestro-runner flows sequentially +5. For iOS: auto-swaps `appId` in flow files to the iOS bundle ID +6. Reports pass/fail results and lists untested features + +## Test Matrix + +| # | Test | What It Validates | +|---|------|------------------| +| 01 | Scan / Connect / Discover | Full happy path -- scan, find BlePlxTest, connect, discover, verify all characteristics | +| 02 | Read Counter | Characteristic read returns incrementing value | +| 03 | Write / Read Echo | Write a base64 value, read it back, verify match | +| 04 | Notify Stream | 5 seconds of notifications -- at least 5 samples, at least 2 distinct values | +| 05 | Indicate Stream | 6 seconds of indications -- at least 3 samples, at least 2 distinct values | +| 06 | MTU Read | MTU value displayed in UI, MTU characteristic readable | +| 07 | Disconnect / Reconnect | Clean disconnect, re-scan, reconnect to same device | +| 08 | Write No Response | Write-without-response echo roundtrip | +| 09 | Scan UUID Filter | Filtered scan by service UUID finds only the test peripheral | +| 10 | Background Mode | Monitoring continues while the app is in the background | +| 11 | L2CAP Channel | Open channel, write data, close channel | + +11 test flows per platform. Run `./run-e2e.sh android` and `./run-e2e.sh ios` separately. + +## Writing New Test Flows + +Test flows are YAML files in `integration-tests/hardware/maestro/`. They follow the [Maestro flow syntax](https://maestro.mobile.dev/reference/commands). + +### Naming Convention + +- Numbered flows (`01-`, `02-`, etc.) run in order +- Shared subflows start with `_` (e.g., `_connect-and-discover.yaml`) +- iOS-specific variants end with `-ios.yaml` (e.g., `10-background-mode-ios.yaml`) + +### Subflow Reuse + +Most tests start with the same scan/connect/discover sequence. Use `runFlow` to include the shared subflow: + +```yaml +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +# Your test-specific steps here +- tapOn: + id: "read-counter-btn" +- extendedWaitUntil: + visible: + id: "counter-value" + timeout: 5000 +``` + +### Element Selection + +Use `id` attributes (React Native `testID` props) for element selection. This is more reliable than text matching, especially across platforms. + +### Timeouts + +BLE operations can be slow. Use `extendedWaitUntil` with generous timeouts instead of `assertVisible`: + +```yaml +# Good -- waits up to 30 seconds +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 30000 + +# Bad -- fails immediately if not visible +- assertVisible: + id: "device-BlePlxTest" +``` + +Note: maestro-runner doesn't support inline `timeout` inside `assertVisible`. Always use `extendedWaitUntil` for anything that might take time. + +### iOS-Specific Flows + +For tests that behave differently on iOS (like background mode), create a separate `-ios.yaml` variant. The run script automatically swaps it in when running iOS tests. + +### Known Limitation: FlatList on Android Fabric + +Tapping items inside a `FlatList` is unreliable on Android with Fabric (New Architecture). The workaround is to use a plain `View` with `.map()` instead of `FlatList` for UI elements that need to be tapped in tests. The example app already uses this pattern for the device list and characteristic list. + +## Building and Installing Apps + +### Example App (Scanner) + +**Android:** +```bash +cd example && npx react-native run-android +``` + +**iOS:** +```bash +cd example/ios +xcodebuild -workspace BlePlxExample.xcworkspace -scheme BlePlxExample \ + -destination 'id=00008130-000A34C12021401C' -configuration Debug build +xcrun devicectl device install app --device 00008130-000A34C12021401C \ + ~/Library/Developer/Xcode/DerivedData/BlePlxExample-*/Build/Products/Debug-iphoneos/BlePlxExample.app +``` + +### BlePlxTest Peripheral + +**Android:** +```bash +cd integration-tests/hardware/test-app # or peripheral project directory +JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" ./gradlew assembleDebug +adb install -r app/build/outputs/apk/debug/app-debug.apk +``` + +**iOS:** +```bash +cd integration-tests/hardware/peripheral-firmware/ble_test_peripheral # or iOS peripheral project +xcodebuild -project BlePlxTest.xcodeproj -scheme BlePlxTest \ + -destination 'id=00008130-000A34C12021401C' -configuration Debug build +xcrun devicectl device install app --device 00008130-000A34C12021401C \ + ~/Library/Developer/Xcode/DerivedData/BlePlxTest-*/Build/Products/Debug-iphoneos/BlePlxTest.app +``` + +## Features NOT Covered by E2E Tests + +Some things can't be practically tested with UI automation (not for lack of trying): + +| Feature | Why | +|---------|-----| +| PHY Requests | Android-only, no observable UI effect | +| Connection Priority | Affects radio timing, not visible in UI | +| Bond State / Pairing | Requires interacting with system pairing dialog | +| State Restoration | Requires the OS to kill and relaunch the app | +| Permission Denied Flows | Can't control system permission dialogs | +| Multiple Simultaneous Connections | Only one test peripheral available | +| Transaction Cancellation | Mid-flight timing is impractical in UI tests | +| Authorization Status | Read-only state, can't toggle programmatically | + +These are covered by unit tests and manual testing during the release process. If you figure out how to automate pairing dialogs, let us know. + +## Troubleshooting Test Runs + +### Nuclear Option: Restart Everything + +When tests get stuck (tunnel dies, WDA won't connect, scans time out): + +1. Restart both phones +2. Reconnect USB cables +3. `pkill -f maestro-runner` on the Mac +4. Restart the tunnel: `sudo pymobiledevice3 remote tunneld -d` +5. Unlock the iPhone (CoreBluetooth reduces advertising when locked) +6. Run tests + +### "No route to host" / pymobiledevice3 Fails + +The tunnel died. Restart it: +```bash +sudo pymobiledevice3 remote tunneld -d +``` + +### WDA Port in Use (iOS Tests) + +The script kills port 8152 between test runs automatically. If you still get "address already in use": +```bash +lsof -ti :8152 | xargs kill -9 +``` + +### BlePlxTest Not Found During Scan + +- Make sure the peripheral app is actually running on the other device +- Phones should be physically near each other (within a few feet) +- iPhone locked = reduced advertising. Keep it unlocked during tests. +- Manually launch the peripheral if needed: + ```bash + # Android + adb shell am start -n com.bleplx.testperipheral/.MainActivity + # iOS + xcrun devicectl device process launch --terminate-existing \ + --device 00008130-000A34C12021401C com.bleplx.testperipheral.ios + ``` diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 000000000..935f14979 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,296 @@ +# Troubleshooting + +BLE on mobile is a minefield. Here are the problems you will encounter and how to fix them. + +--- + +## "BLE state is Unknown / PoweredOff" + +**Symptom:** `manager.state()` returns `Unknown` or `PoweredOff`. + +**Causes and fixes:** + +1. **You're running on a simulator/emulator.** BLE doesn't work there. Use a physical device. There is no workaround, no mock, no clever hack. Physical device. + +2. **Bluetooth is actually off.** Check Settings on the device. On iOS, also check Control Center -- the Bluetooth icon should be blue, not gray. + +3. **You're calling `state()` too early.** On iOS, the BLE stack reports `Unknown` for a brief moment after app launch while CoreBluetooth initializes. Use `onStateChange` to wait for the real state: + + ```typescript + const sub = manager.onStateChange(state => { + if (state === State.PoweredOn) { + sub.remove(); + startYourBleStuff(); + } + }, true); // true = also emit current state immediately + ``` + +4. **iOS: Bluetooth authorization denied.** If the user denied Bluetooth permission, the state shows as `Unauthorized`. Check `getAuthorizationStatus()` and prompt the user to enable it in Settings. + +--- + +## "Scan doesn't find any devices" + +**Symptom:** `startDeviceScan` callback never fires, or fires with zero devices. + +**Causes and fixes:** + +1. **Permissions not granted.** This is the #1 cause on Android. Check that you've requested and received: + - Android 12+: `BLUETOOTH_SCAN` and `BLUETOOTH_CONNECT` + - Android 11 and below: `ACCESS_FINE_LOCATION` + - Without permissions, scans silently return nothing. No error, no crash, just... silence. + +2. **The peripheral isn't advertising.** Verify with a third-party BLE scanner app (nRF Connect, LightBlue) that the device is actually visible. + +3. **Android scan throttling.** Android 7+ limits scan starts to approximately 5 per 30 seconds. If you're rapidly starting/stopping scans (common during development), you'll hit this limit and get zero results. Wait 30 seconds and try again. In production, don't start/stop scans rapidly. + +4. **iOS background scanning without service UUIDs.** If your app is backgrounded on iOS and you pass `null` for `serviceUuids`, you'll get zero results. Background scanning MUST specify service UUID filters. + +5. **Wrong service UUID format.** UUIDs must be the full 128-bit format (`0000180a-0000-1000-8000-00805f9b34fb`) or the short 16-bit format (`180a`). Verify you're using the right one. + +6. **`legacyScan` filtering out BLE 5.0 devices.** If the peripheral uses BLE 5.0 Extended Advertising, set `legacyScan: false` in scan options: + + ```typescript + manager.startDeviceScan(null, { legacyScan: false }, callback); + ``` + +--- + +## "Connection times out" + +**Symptom:** `connectToDevice` rejects with a timeout error. + +**Causes and fixes:** + +1. **Device is out of range or not advertising.** BLE range varies wildly -- from 1 meter to 100 meters depending on the environment, device, and antenna. Move closer. + +2. **Device is already connected to something else.** Most BLE peripherals only allow one central connection at a time. Disconnect from nRF Connect or whatever other app is hogging the connection. + +3. **Android: `autoConnect: false` with slow peripherals.** Direct connection (`autoConnect: false`, the default) has a ~30 second system timeout. If the peripheral is slow to respond, try `autoConnect: true` for a more patient background connection. Or set an explicit `timeout` with `retries`: + + ```typescript + await manager.connectToDevice(deviceId, { + timeout: 15000, + retries: 3, + retryDelay: 2000, + }); + ``` + +4. **Android: GATT 133 ("the error code of despair").** GATT status 133 is Android's catch-all "something went wrong" error. Common causes: + - Too many simultaneous connections (Android typically supports 6-7) + - Previous connection wasn't cleaned up properly + - Bluetooth stack corruption + + Fix: Clear the Bluetooth cache (Settings > Apps > Bluetooth > Storage > Clear Cache), toggle Bluetooth off/on, or restart the phone. In code, use the `retries` option. + +5. **iOS: device not in range after scan.** iOS scan results include cached devices that may no longer be in range. The `connectToDevice` call will hang until timeout. Always check `isConnectable` from the scan result. + +--- + +## Android 12+ Permission Dance + +Android 12 (API 31) split the old blanket Bluetooth permission into granular ones. Here's what you need and when: + +| Operation | Permission Required | +|-----------|-------------------| +| Scanning | `BLUETOOTH_SCAN` | +| Connecting / reading / writing | `BLUETOOTH_CONNECT` | +| Scanning on Android 11 and below | `ACCESS_FINE_LOCATION` | + +**Common mistakes:** + +- **Forgetting `BLUETOOTH_CONNECT`.** You can scan and find devices, but connecting fails silently or crashes. +- **Not requesting at runtime.** Declaring permissions in `AndroidManifest.xml` is necessary but not sufficient. You must also call `PermissionsAndroid.request()` at runtime. +- **Requesting before the Activity is ready.** Permission requests need an active Activity. Don't request permissions in module constructors or static initializers. + +--- + +## iOS Background Mode Setup + +For BLE to work while your app is backgrounded on iOS: + +1. **Enable the capability:** Xcode > Target > Signing & Capabilities > Background Modes > "Uses Bluetooth LE accessories" + +2. **Or in Info.plist:** + ```xml + UIBackgroundModes + + bluetooth-central + + ``` + +3. **Scan with service UUID filters:** Background scans with `serviceUuids: null` return nothing. + +4. **Use state restoration:** Pass a `restoreStateIdentifier` to `createClient()` so the system can relaunch your app for BLE events. + +**Things that DON'T work in background on iOS:** +- Scanning without service UUID filters +- `allowDuplicates` is ignored in background +- Scan intervals increase significantly +- L2CAP channels do NOT wake suspended apps -- only GATT characteristic notifications do + +--- + +## "Monitor stops receiving after backgrounding" + +**Symptom:** Characteristic notifications work in the foreground but stop when the app goes to the background. + +**iOS:** +- Enable the `bluetooth-central` background mode (see above) +- Make sure your subscription is on a GATT characteristic (notifications/indications), not an L2CAP channel +- The system may throttle delivery in the background. Notifications are still received but may be delivered in batches when the app returns to the foreground. + +**Android:** +- You need a foreground service with `android:foregroundServiceType="connectedDevice"` to maintain BLE connections in the background +- Without a foreground service, Android 12+ restricts BLE operations when the app is backgrounded +- The Expo config plugin's `isBackgroundEnabled` flag adds the necessary hardware feature declaration, but you still need to implement the foreground service yourself + +--- + +## Metro Bundler + Physical Device Setup + +**"Unable to load script" / blank white screen on device:** + +The Metro bundler needs to be reachable from your physical device. + +**Android:** + +```bash +# Forward Metro's port over USB +adb reverse tcp:8081 tcp:8081 +npx react-native start +``` + +**iOS:** + +Metro should work over the local network if your Mac and iPhone are on the same Wi-Fi. If it doesn't: +1. Make sure your Mac's firewall isn't blocking port 8081 +2. In Xcode, check the IP address in your build configuration +3. As a last resort, set `BUNDLE_URL` manually + +--- + +## Debugging with Native Logs + +When JavaScript error messages aren't enough (and they often aren't), look at the native logs. + +### Android (adb logcat) + +```bash +# Filter for BLE-related logs +adb logcat | grep -iE "ble|bluetooth|gatt|bleplx" + +# More verbose -- includes system Bluetooth stack +adb logcat | grep -iE "bt_|bluetooth|BleManager|BlePlx" + +# Common useful tags: +# BlePlxModule - TurboModule entry point +# BleManagerWrapper - Nordic BLE Library operations +# bt_btm - Android Bluetooth stack +# BluetoothGatt - GATT operations +``` + +### iOS (Xcode Console) + +1. Connect your device and open Xcode +2. Window > Devices and Simulators > select your device > Open Console +3. Filter for: `BLE`, `CoreBluetooth`, `CBManager`, `BlePlx` + +Or use the Console.app: +1. Open `/Applications/Utilities/Console.app` +2. Select your device +3. Filter by process name or search for BLE-related terms + +**iOS CoreBluetooth logs are notoriously sparse.** The most useful debug tool is often adding `print()` statements to the native Swift code temporarily. + +### React Native Logs + +```bash +# Android +npx react-native log-android + +# iOS +npx react-native log-ios +``` + +--- + +## "createClient() was not called" + +**Symptom:** Error `ManagerNotInitialized` on any BLE operation. + +In v4, you must call `await manager.createClient()` before any other BLE operation. Unlike v3, the constructor doesn't initialize the native client automatically. + +```typescript +const manager = new BleManager(); +await manager.createClient(); // Don't forget this! +``` + +--- + +## "NativeBlePlx TurboModule not found" + +**Symptom:** Error on import or first use. + +**Causes:** + +1. **Not linked properly.** Run `cd ios && pod install` and rebuild. +2. **Using Expo Go.** This library requires native code. Use a development build (`npx expo prebuild`). +3. **Old architecture.** v4 requires React Native 0.82+ with New Architecture. The Bridge-based native modules don't work. +4. **Missing `pod install` after upgrade.** Always run `pod install` after updating the package. + +--- + +## "Characteristic not found" After Successful Discovery + +**Symptom:** `discoverAllServicesAndCharacteristics` succeeds, but `readCharacteristicForDevice` fails with `CharacteristicNotFound`. + +**Causes:** + +1. **Wrong UUID.** BLE UUIDs are case-insensitive but format-sensitive. The standard Bluetooth SIG services use short UUIDs (`180a`) which expand to `0000180a-0000-1000-8000-00805f9b34fb`. Custom services use the full 128-bit form. Double-check your UUIDs against the peripheral's documentation or nRF Connect. + +2. **Service/characteristic requires encryption.** Some characteristics are hidden until the connection is encrypted (bonded). Connect, bond, then re-discover. + +3. **Discovery timed out silently.** If the peripheral has many services, discovery can take several seconds. Make sure you `await` the discovery promise. + +--- + +## Android-Specific Issues + +### GATT Error 133 + +The most common and least helpful Android BLE error. It means "something went wrong at the GATT level." Possible causes include: +- Bluetooth cache corruption +- Too many connections +- Hardware-specific firmware bugs +- Solar flares (not really, but it feels that way) + +Fixes: +- Toggle Bluetooth off and on +- Clear Bluetooth cache (Settings > Apps > Show system > Bluetooth > Clear Cache) +- Use `retries` in `connectToDevice` +- Restart the phone (nuclear option) + +### "App was not granted BLUETOOTH_SCAN permission" + +On Android 12+, you need runtime `BLUETOOTH_SCAN` permission before scanning. This is in addition to the manifest declaration. See the [Getting Started guide](GETTING_STARTED.md#runtime-permissions) for the full permission request flow. + +### Scan Results Empty After Multiple Start/Stop Cycles + +Android throttles scan starts. After ~5 start/stop cycles in 30 seconds, the system silently returns zero results. Wait 30 seconds or don't cycle scans rapidly. + +--- + +## iOS-Specific Issues + +### "Bluetooth permission denied" But User Was Never Asked + +This happens when `NSBluetoothAlwaysUsageDescription` is missing from `Info.plist`. iOS requires this key -- without it, the system denies Bluetooth access without showing a prompt. + +### State Shows "Unauthorized" + +The user denied the Bluetooth permission prompt, or it was denied via Settings > Privacy > Bluetooth. Direct the user to Settings to re-enable it. You cannot programmatically request the permission again after it's been denied. + +### "CBATTError Domain=6" (Offset / Request Not Supported) + +The characteristic doesn't support the operation you're trying. Check `isReadable`, `isWritableWithResponse`, `isWritableWithoutResponse` on the `CharacteristicInfo` before attempting the operation. diff --git a/docs/ble-mobile-research.md b/docs/ble-mobile-research.md new file mode 100644 index 000000000..58298e19a --- /dev/null +++ b/docs/ble-mobile-research.md @@ -0,0 +1,300 @@ +# Rewriting react-native-ble-plx as a TurboModule: the definitive guide + +**A TurboModule rewrite of react-native-ble-plx using Nordic's Android-BLE-Library (Kotlin) and CoreBluetooth (Swift) is both feasible and urgently needed**, since the current v3.5.1 crashes under React Native's New Architecture (enabled by default since RN 0.76). This guide covers every critical pitfall, from Codegen's type system limitations to CoreBluetooth's non-Sendable objects to Android's scan throttling — distilled into actionable patterns for implementation. The existing library's bridge-based architecture, Base64 serialization overhead, and missing features (L2CAP, BLE 5.0 PHY, GATT server role) make a clean TurboModule rewrite the right path forward rather than incremental migration. + +--- + +## TurboModule Codegen: what works and what will bite you + +The Codegen spec file (`NativeBlePlx.ts`) must follow strict naming conventions: the file must be prefixed with `Native`, the interface must be named exactly `Spec` extending `TurboModule`, and it must export via `TurboModuleRegistry`. Breaking any of these rules causes silent failures or parser errors. + +**Supported types** include `string`, `number`, `boolean`, `Float`, `Double`, `Int32`, `Promise`, `Readonly<{...}>` for typed objects, `ReadonlyArray`, nullable types via `| null`, optional object fields via `?`, and single-fire callbacks. For untyped escape hatches, `UnsafeObject` maps to `NSDictionary`/`ReadableMap`. + +**Critical limitations that directly impact a BLE library design:** + +- **Union types are not supported.** You cannot define `type WriteType = 'withResponse' | 'withoutResponse'` as a parameter type — the codegen parser rejects `TSUnionType`. Use string parameters with runtime validation instead, or use enum syntax for numeric enums. +- **All types must be defined inline in the spec file.** Importing types from other files is completely ignored by Codegen. Every `Readonly<{...}>` type alias must live in `NativeBlePlx.ts` itself. The React Native team acknowledges this is "not optimal" but fixing it would be a "big breaking change" — still unresolved as of June 2025. +- **`Map` is not supported.** Use `Readonly<{[key: string]: V}>` for string-keyed dictionaries. +- **Generics, tuples, and multiple interface inheritance are not supported.** +- **Platform-specific spec files (`.ios.ts`/`.android.ts`) are ignored.** Use a single spec with no-op methods on each platform. +- **Callbacks are single-fire.** You cannot hold a callback reference and invoke it repeatedly from native. For BLE scan results and characteristic notifications, use `EventEmitter` instead. + +**Event emitters** use the new `CodegenTypes.EventEmitter` pattern (RN 0.76+), which is type-safe and generates platform-specific emit methods. Define events as `readonly` properties on the spec: + +```typescript +import type { TurboModule, CodegenTypes } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export type ScanResult = Readonly<{ + id: string; + name: string | null; + rssi: number; + serviceUuids: ReadonlyArray; + manufacturerData: string | null; // Base64-encoded +}>; + +export interface Spec extends TurboModule { + startScan(serviceUuids: string[], options: string): void; + stopScan(): Promise; + connect(deviceId: string, options: string): Promise; + readonly onScanResult: CodegenTypes.EventEmitter; + readonly onDisconnection: CodegenTypes.EventEmitter>; +} + +export default TurboModuleRegistry.get('NativeBlePlx'); +``` + +On the native side, Codegen generates `emitOnScanResult` / `emitOnDisconnection` methods on the base class. JS subscribes via `NativeBlePlx?.onScanResult((result) => {...})` — no more `NativeEventEmitter` boilerplate. **Import path gotcha**: use `CodegenTypes.EventEmitter` via `import type { CodegenTypes } from 'react-native'` rather than deep imports, which break on some RN 0.76.x versions. + +Use `TurboModuleRegistry.get` (nullable) rather than `getEnforcing` for graceful degradation. Make new methods optional (`method?: () => void`) for forward compatibility with older native binaries. + +**The `codegenConfig` in package.json** should be: + +```json +{ + "codegenConfig": { + "name": "NativeBlePlxSpec", + "type": "modules", + "jsSrcsDir": "src/specs", + "android": { + "javaPackageName": "com.bleplx" + } + } +} +``` + +One `codegenConfig` per package; arrays are not supported. Multiple spec files in the same `jsSrcsDir` are discovered automatically. **Avoid `includesGeneratedCode: true`** unless you plan to regenerate for every RN major version — libraries pre-generated with RN <0.84 break on 0.84+ due to `ResultT` type alias changes. + +--- + +## Android: Kotlin + Nordic BLE Library integration patterns + +**Use Nordic Android-BLE-Library `2.11.0`** (`no.nordicsemi.android:ble:2.11.0` + `ble-ktx:2.11.0`) and Scanner Compat `1.6.0` (`no.nordicsemi.android.support.v18:scanner:1.6.0`). The pure-Kotlin rewrite (`kotlin.ble:client-android`) is explicitly "not recommended for production" by Nordic. The stable 2.x library with Kotlin coroutine extensions via `ble-ktx` is the correct choice. + +**BleManager architecture**: Subclass `BleManager`, override `isRequiredServiceSupported(gatt)` to discover and cache characteristic references, `onServicesInvalidated()` to null them, and optionally `initialize()` to queue setup operations (enable notifications, request MTU). All GATT operations are **automatically serialized** by Nordic's internal queue — no manual queuing needed on Android. + +```kotlin +class DeviceBleManager(context: Context) : BleManager(context) { + private var txChar: BluetoothGattCharacteristic? = null + private var rxChar: BluetoothGattCharacteristic? = null + + override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { + val service = gatt.getService(SERVICE_UUID) ?: return false + txChar = service.getCharacteristic(TX_UUID) + rxChar = service.getCharacteristic(RX_UUID) + return txChar != null && rxChar != null + } + + override fun initialize() { + requestMtu(517).enqueue() + enableNotifications(rxChar).enqueue() + } + + override fun onServicesInvalidated() { + txChar = null + rxChar = null + } +} +``` + +With `ble-ktx`, operations become suspending functions: `connect(device).retry(3, 100).timeout(15_000).suspend()`. Use `suspendForResponse()` for reads with parsed responses. + +**The seven critical Android BLE gotchas:** + +1. **Error 133 (GATT_ERROR)** is Android's catch-all mystery error. **Always** use `.retry(3, 100)` on connections. Common triggers: reconnecting too fast, Samsung stack bugs, and Bluetooth adapter congestion. + +2. **MTU must be explicitly requested** — Android defaults to 23 bytes (20 usable). Call `requestMtu(517)` in `initialize()`. If the peripheral initiates MTU exchange instead, `onMtuChanged()` may not fire on some Android versions. Always request from the central (Android) side. + +3. **Scan throttling**: Android 7.0+ limits apps to **5 `startScan()` calls per 30 seconds**. Exceeding this silently returns zero results with no error callback. Scans >30 minutes are auto-downgraded to `SCAN_MODE_OPPORTUNISTIC`. The Scanner Compat library does **not** automatically handle this — you must implement throttle protection: debounce start/stop cycles, reuse long-running scans, and space restarts by ≥6 seconds. + +4. **Android 12+ permissions** require `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT`, and optionally `BLUETOOTH_ADVERTISE` at runtime (shown as "Nearby Devices" to users). Add `android:maxSdkVersion="30"` to legacy `BLUETOOTH`/`BLUETOOTH_ADMIN` and `ACCESS_FINE_LOCATION` permissions. The `neverForLocation` flag on `BLUETOOTH_SCAN` filters out some BLE beacons — only use if you truly never derive location from scans. + +5. **Android 14+ foreground service types**: Background BLE requires a foreground service with `android:foregroundServiceType="connectedDevice"`. Android 12+ restricts starting foreground services from background. For background scanning, use foreground services (most reliable), `PendingIntent`-based scanning (system-managed), or `CompanionDeviceManager` (Android 12+, limited). + +6. **Device cache after firmware updates**: Android caches GATT service structure. After OTA updates that change services, use `refreshDeviceCache()` (reflection-based) or `ensureBond()` to force re-discovery. + +7. **Bonding security gap**: `getBondState()` only checks whether bond info exists, not whether the link is encrypted. An attacker can spoof MAC addresses and connect unencrypted. Use Nordic's `ensureBond()` to verify actual encryption, or read a protected characteristic to trigger automatic pairing (matches iOS behavior). + +**Thread safety pattern for the TurboModule:** + +```kotlin +class BlePlxModule(reactContext: ReactApplicationContext) + : NativeBlePlxSpec(reactContext) { + + private val moduleScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) + private val connectedManagers = ConcurrentHashMap() + + override fun connect(address: String, promise: Promise) { + moduleScope.launch { + try { + val device = bluetoothAdapter.getRemoteDevice(address) + val manager = DeviceBleManager(reactApplicationContext) + manager.connect(device).retry(3, 100).timeout(15_000).suspend() + connectedManagers[address] = manager + promise.resolve(true) + } catch (e: Exception) { + promise.reject("BLE_CONNECT_ERROR", e.message, e) + } + } + } + + override fun invalidate() { + super.invalidate() + moduleScope.cancel() + connectedManagers.values.forEach { it.close() } + connectedManagers.clear() + } +} +``` + +**Always cancel the `CoroutineScope` in `invalidate()`** to prevent leaks on RN reload. Use `ConcurrentHashMap` for shared mutable state (device maps). `promise.resolve()` / `promise.reject()` are thread-safe and can be called from any thread. Use `Dispatchers.Default` for BLE work, never `Dispatchers.Main`. + +--- + +## iOS: Swift + CoreBluetooth with the actor isolation challenge + +**CoreBluetooth has no async/await support as of iOS 18.** Apple has not modernized the framework — it remains entirely delegate-based. You must wrap it manually using `withCheckedThrowingContinuation` for one-shot operations and `AsyncStream` for ongoing events. + +**The fundamental Swift concurrency challenge**: `CBPeripheral`, `CBCentralManager`, and other CoreBluetooth objects do **not conform to `Sendable`**. Passing them across actor boundaries violates Swift 6 strict concurrency. Apple provides no official guidance for resolving this. + +**The recommended architecture uses a custom actor executor** (Swift 5.9+) backed by the same `DispatchQueue` that CoreBluetooth uses: + +```swift +actor BLEActor { + let queue = DispatchQueue(label: "com.bleplx.ble") + nonisolated var unownedExecutor: UnownedSerialExecutor { + queue.asUnownedSerialExecutor() + } + + private lazy var centralManager = CBCentralManager( + delegate: delegateHandler, queue: queue + ) + // Actor-isolated code now runs on the same queue as CoreBluetooth +} +``` + +This eliminates the mismatch between actor isolation and CoreBluetooth's dispatch queue requirement. Without custom executors, the alternative is keeping all CoreBluetooth interaction in a plain `NSObject` class on the BLE queue, then bridging extracted **Sendable values** (UUIDs, Data, Strings — not CB objects) into actors via continuations. + +**Practical workarounds for Swift 6 concurrency:** +- Use `@preconcurrency import CoreBluetooth` to silence Sendable warnings from the framework +- Mark wrapper types as `@unchecked Sendable` when you guarantee thread safety through your dispatch queue +- Mark delegate conformance methods as `nonisolated` +- Never pass `CBPeripheral` or `CBCharacteristic` across actor boundaries — extract the data first + +**CoreBluetooth does NOT queue GATT operations.** Unlike Nordic on Android, rapid successive `readValue`/`writeValue` calls fail silently or produce ATT errors. You **must** implement a serial operation queue: + +```swift +actor GATTOperationQueue { + private var pending: [(work: () -> Void, continuation: CheckedContinuation)] = [] + private var isExecuting = false + + func enqueue(_ operation: @escaping () -> Void) async throws -> Data? { + try await withCheckedThrowingContinuation { continuation in + pending.append((work: operation, continuation: continuation)) + if !isExecuting { executeNext() } + } + } + + func operationCompleted(result: Result) { + // Resume current continuation, then executeNext() + } +} +``` + +Rules: one outstanding operation per type at a time, manual timeouts (10-30 seconds, since CoreBluetooth has none built-in), and flow control for write-without-response via `canSendWriteWithoutResponse` and the `peripheralIsReady(toSendWriteWithoutResponse:)` delegate. + +**State restoration** requires initializing `CBCentralManager` with `CBCentralManagerOptionRestoreIdentifierKey`, implementing `willRestoreState` to re-set delegates and retain peripherals, and checking `UIApplication.LaunchOptionsKey.bluetoothCentrals` in the app delegate. Critical gotchas: `willRestoreState` fires **before** `centralManagerDidUpdateState`; the central manager does **not** retain strong references to peripherals (you must store them yourself); and state restoration only works for system-terminated apps — force-quit by user disables it. + +**Background BLE on iOS** requires `UIBackgroundModes` with `bluetooth-central` in Info.plist, and `NSBluetoothAlwaysUsageDescription`. Background scanning **must specify service UUID filters** — passing `nil` returns zero results in background. `CBCentralManagerScanOptionAllowDuplicatesKey` is ignored in background. Scan intervals increase significantly. + +**Extended advertising support is hardware-dependent and limited.** Check `CBCentralManager.supportsFeatures(.extendedScanAndConnect)`. Only LE 2M PHY extended ads are supported — **Coded PHY (long range) is not supported on iOS** for scanning. No API exists to control scan window/interval. + +**L2CAP channels** (`CBL2CAPChannel`, available since iOS 11) require: dynamically assigned PSMs (read from a GATT characteristic, no static PSMs on iOS), strong retention of both the channel and peripheral (losing reference = instant disconnect with error 436), manually opening input/output streams and scheduling them on the correct RunLoop, and reading/writing from the same DispatchQueue as the stream schedule. L2CAP does **not** wake suspended apps — only GATT characteristic notifications do that. + +**The iOS TurboModule requires an Objective-C++ entry point.** Codegen generates Obj-C headers; pure-Swift TurboModules are not possible. The architecture is: + +``` +JS Spec → Codegen → Obj-C++ TurboModule (.mm) → Swift BLEModuleImpl → BLEActor → CoreBluetooth +``` + +The `.mm` file conforms to the generated spec and delegates to a Swift `@objc public class`. All Swift classes/methods exposed to Obj-C need `@objc public`. + +--- + +## Cross-platform BLE differences that the abstraction layer must handle + +**MTU negotiation** is the most impactful divergence. iOS automatically negotiates **~185 bytes** upon connection with no developer API. Android defaults to **23 bytes** and requires explicit `requestMtu(517)`. A BLE library must auto-request MTU on Android during connection setup — the current ble-plx leaves this to developers, causing silent data truncation as the #1 reported issue. + +**Device identifiers** are fundamentally incompatible: Android uses MAC addresses (`AA:BB:CC:DD:EE:FF`), iOS uses opaque UUIDs generated per-phone (not per-app). The UUID can change after Bluetooth settings reset. There is **no cross-platform stable identifier** — embed unique IDs in GATT characteristics or manufacturer advertising data. + +**Notification/indication subscription** differs critically: iOS handles CCCD descriptor writes automatically when you call `setNotifyValue(true)`, while Android requires manually writing `0x0001` (notify) or `0x0002` (indicate) to descriptor `0x2902`. Forgetting the Android CCCD write is the second most common BLE bug in React Native apps. + +**Write type enforcement** diverges: iOS strictly validates characteristic properties and silently ignores mismatched write types, while Android is permissive and may allow write-without-response on characteristics that don't advertise it. Write-without-response bursts that work on Android may stall on iOS due to internal flow control (`canSendWriteWithoutResponse`). + +**Connection parameters** cannot be set from either platform's app-level API — only the peripheral firmware can request changes. iOS enforces stricter minimum intervals (**15ms** for non-HID vs Android's **7.5ms**). Both platforms cache GATT tables, but the cache invalidation mechanisms differ completely. + +--- + +## What react-native-ble-plx v3.5.1 gets wrong + +The current library has **27+ open issues** and crashes under React Native's New Architecture (issues #1277, #1278). It uses the classic `RCT_EXPORT_MODULE` bridge pattern — all BLE data crosses as JSON, adding latency especially for high-frequency notifications. There is no TurboModule spec, no Codegen integration, and no official roadmap for migration. + +**Architecture problems a rewrite should fix:** + +- **Base64 encoding overhead**: All data traverses the bridge as Base64 strings. TurboModules with JSI can pass typed data more efficiently. +- **Singleton BleManager**: v3 forces a singleton pattern, limiting flexibility for multi-manager scenarios. +- **No GATT operation queue on iOS**: The current library doesn't properly serialize CoreBluetooth operations, causing silent failures under load. +- **Missing features**: No L2CAP support, no BLE 5.0 PHY selection, no GATT server (peripheral) role, no explicit bonding API, incomplete extended advertising support, and no built-in permission management. +- **Android disconnect bug**: After v2→v3 migration, devices disconnect after 5 seconds on Android (#1157) with no error, suggesting lifecycle management issues. +- **Scan result disappearance**: Devices don't reappear in scans after disconnecting (#1279), pointing to improper scan state management. + +The competing library `react-native-ble-manager` (~44K weekly downloads vs ble-plx's ~90K) offers simpler APIs, peripheral role support, and bonded device retrieval, but lacks ble-plx's granular state control. Neither library supports the New Architecture. **Flutter BLE plugins are generally considered more stable** by BLE experts — a well-architected TurboModule rewrite could close this gap. + +--- + +## Library packaging: podspec, Gradle, and distribution + +**Use `create-react-native-library`** (Callstack's official tool, recommended by React Native docs) to scaffold, then customize for Swift/BLE needs: + +```bash +npx create-react-native-library@latest react-native-ble-plx +# Select: Turbo module, Kotlin & Objective-C +# Then manually add Swift files and update podspec +``` + +**Podspec for Swift** — include `.swift` in source_files, make headers private, use `install_modules_dependencies`: + +```ruby +Pod::Spec.new do |s| + s.name = "react-native-ble-plx" + s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" + s.private_header_files = "ios/**/*.h" + install_modules_dependencies(s) # Handles all New Architecture deps +end +``` + +**Android `build.gradle`** must apply `com.facebook.react` plugin (integrates Codegen), use `safeExtGet` for SDK versions, and add Nordic dependencies: + +```groovy +apply plugin: 'com.facebook.react' +apply plugin: 'org.jetbrains.kotlin.android' + +dependencies { + implementation 'com.facebook.react:react-android' + implementation 'no.nordicsemi.android:ble:2.11.0' + implementation 'no.nordicsemi.android:ble-ktx:2.11.0' + implementation 'no.nordicsemi.android.support.v18:scanner:1.6.0' +} +``` + +**For Expo compatibility**, ship a config plugin that adds `NSBluetoothAlwaysUsageDescription` to Info.plist and BLE permissions to AndroidManifest. Expo SDK 53+ has full New Architecture support; Expo SDK 55+ **requires** it. The library works with development builds, not Expo Go. + +**Module name must match exactly** across: the `TurboModuleRegistry.get('NativeBlePlx')` call, the native `NAME` constant, and the Kotlin/Obj-C class registration. A mismatch causes silent module-not-found errors. + +--- + +## Conclusion + +The rewrite should prioritize three architectural decisions that differ most from the current ble-plx implementation. First, **auto-negotiate MTU on Android** during connection setup rather than leaving it to developers — this alone would eliminate the most common category of user-reported bugs. Second, **implement a proper GATT operation queue on iOS** using Swift actor isolation with a custom executor backed by CoreBluetooth's dispatch queue — the current library's lack of serialization causes silent operation failures. Third, **use `CodegenTypes.EventEmitter`** for all streaming data (scan results, notifications, disconnections) rather than the legacy `NativeEventEmitter` pattern — this provides type safety, automatic subscription cleanup, and eliminates the `addListener`/`removeListeners` boilerplate. + +The most likely implementation pain points will be: the Codegen requirement to inline all types in a single spec file (expect a large `NativeBlePlx.ts`), the Obj-C++ bridging layer requirement on iOS preventing pure Swift (plan the `.mm` → Swift delegation pattern early), and Android's scan throttling (build debouncing into the scan API itself rather than exposing raw start/stop). Nordic's BleManager handles GATT queuing on Android, but you must still manage connection lifecycle, coroutine scope cancellation on RN reload, and concurrent device map synchronization carefully. \ No newline at end of file diff --git a/docs/plans/2026-03-15-plan-a-turbomodule-js-api.md b/docs/plans/2026-03-15-plan-a-turbomodule-js-api.md new file mode 100644 index 000000000..30f2889dc --- /dev/null +++ b/docs/plans/2026-03-15-plan-a-turbomodule-js-api.md @@ -0,0 +1,528 @@ +# Plan A: TurboModule Infrastructure + JS/TS API + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Create the TurboModule Codegen spec, TypeScript API layer, event batching, and error model for react-native-ble-plx v4.0. + +**Architecture:** Codegen spec (`NativeBlePlx.ts`) defines the JS↔Native contract with typed EventEmitters. `BleManager.ts` wraps the native module with a developer-friendly API. `EventBatcher.ts` handles notification backpressure. All types use `as const` objects (not enums). + +**Tech Stack:** TypeScript, React Native 0.82+ TurboModule Codegen, Jest + +**Spec:** `docs/specs/2026-03-15-v4-turbomodule-rewrite.md` + +--- + +## File Structure + +``` +src/ +├── specs/ +│ └── NativeBlePlx.ts ← Codegen spec (THE contract) +├── BleManager.ts ← Public API class +├── Device.ts ← Device wrapper +├── Characteristic.ts ← Characteristic wrapper +├── Service.ts ← Service wrapper +├── Descriptor.ts ← Descriptor wrapper +├── BleError.ts ← Unified error model + error codes +├── types.ts ← Public TypeScript types (const objects) +├── EventBatcher.ts ← Notification backpressure +├── index.ts ← Public exports +__tests__/ +├── BleManager.test.ts +├── EventBatcher.test.ts +├── BleError.test.ts +├── protocol.test.ts ← Binary parsing tests +``` + +--- + +## Chunk 1: Codegen Spec + Types + Error Model + +### Task 1: Codegen spec file + +**Files:** +- Create: `src/specs/NativeBlePlx.ts` + +- [ ] **Step 1: Create the Codegen spec with all typed interfaces and EventEmitters** + +This is the most important file — it generates the native bindings. All types MUST be inline. Copy the complete spec from the design doc Section 5a. Include: +- All `Readonly<{}>` type aliases (DeviceInfo, CharacteristicInfo, ScanResult, etc.) +- All method signatures with typed parameters (no `Object` escape hatches) +- All `readonly onXxx: CodegenTypes.EventEmitter` declarations +- `TurboModuleRegistry.get('NativeBlePlx')` (nullable, not getEnforcing) + +- [ ] **Step 2: Add codegenConfig to package.json** + +```json +{ + "codegenConfig": { + "name": "NativeBlePlxSpec", + "type": "modules", + "jsSrcsDir": "src/specs", + "android": { + "javaPackageName": "com.bleplx" + } + } +} +``` + +- [ ] **Step 3: Verify Codegen parses the spec via build smoke test** + +Codegen runs automatically during platform builds — there is no standalone CLI command. Verify by running: + +```bash +# Android: Gradle will invoke Codegen +cd example && cd android && ./gradlew generateCodegenArtifactsFromSchema + +# iOS: pod install invokes Codegen +cd example && cd ios && pod install +``` + +Expected: native headers generated in build output without Codegen parse errors. + +- [ ] **Step 4: Commit** + +```bash +git add src/specs/NativeBlePlx.ts package.json +git commit -m "feat: TurboModule Codegen spec with typed EventEmitters" +``` + +--- + +### Task 2: TypeScript types and error model + +**Files:** +- Create: `src/types.ts` +- Create: `src/BleError.ts` + +- [ ] **Step 1: Create types.ts with const objects (not enums)** + +```typescript +// src/types.ts — Public types using const objects with as const + +export const State = { + Unknown: 'Unknown', + Resetting: 'Resetting', + Unsupported: 'Unsupported', + Unauthorized: 'Unauthorized', + PoweredOff: 'PoweredOff', + PoweredOn: 'PoweredOn', +} as const; +export type State = typeof State[keyof typeof State]; + +export const LogLevel = { + None: 'None', + Verbose: 'Verbose', + Debug: 'Debug', + Info: 'Info', + Warning: 'Warning', + Error: 'Error', +} as const; +export type LogLevel = typeof LogLevel[keyof typeof LogLevel]; + +export const ConnectionPriority = { + Balanced: 0, + High: 1, + LowPower: 2, +} as const; +export type ConnectionPriority = typeof ConnectionPriority[keyof typeof ConnectionPriority]; + +export const ConnectionState = { + Disconnected: 'disconnected', + Connecting: 'connecting', + Connected: 'connected', + Disconnecting: 'disconnecting', +} as const; +export type ConnectionState = typeof ConnectionState[keyof typeof ConnectionState]; + +export interface ScanOptions { + scanMode?: number; + callbackType?: number; + legacyScan?: boolean; + allowDuplicates?: boolean; +} + +export interface ConnectOptions { + autoConnect?: boolean; + timeout?: number; + retries?: number; + retryDelay?: number; + requestMtu?: number; +} +``` + +- [ ] **Step 2: Create BleError.ts with unified error codes** + +```typescript +// src/BleError.ts — Unified cross-platform error model + +export const BleErrorCode = { + DeviceNotFound: 0, + DeviceDisconnected: 1, + ConnectionFailed: 2, + ConnectionTimeout: 3, + OperationCancelled: 100, + OperationTimeout: 101, + OperationNotSupported: 102, + OperationInProgress: 103, + CharacteristicNotFound: 200, + ServiceNotFound: 201, + DescriptorNotFound: 202, + CharacteristicWriteFailed: 203, + CharacteristicReadFailed: 204, + MTUNegotiationFailed: 205, + BluetoothUnauthorized: 300, + BluetoothPoweredOff: 301, + LocationPermissionDenied: 302, + ScanPermissionDenied: 303, + ConnectPermissionDenied: 304, + ManagerNotInitialized: 400, + ManagerDestroyed: 401, + BondingFailed: 500, + BondLost: 501, + PairingRejected: 502, + L2CAPChannelFailed: 600, + L2CAPChannelClosed: 601, + PhyNegotiationFailed: 700, + ScanFailed: 800, + ScanThrottled: 801, + UnknownError: 999, +} as const; +export type BleErrorCode = typeof BleErrorCode[keyof typeof BleErrorCode]; + +export class BleError extends Error { + readonly code: BleErrorCode; + readonly isRetryable: boolean; + readonly deviceId?: string; + readonly serviceUUID?: string; + readonly characteristicUUID?: string; + readonly operation?: string; + readonly platform: 'android' | 'ios'; + readonly nativeDomain?: string; + readonly nativeCode?: number; + readonly gattStatus?: number; + readonly attErrorCode?: number; + + constructor(errorInfo: { + code: number; + message: string; + isRetryable: boolean; + platform: string; + deviceId?: string | null; + serviceUuid?: string | null; + characteristicUuid?: string | null; + operation?: string | null; + nativeDomain?: string | null; + nativeCode?: number | null; + gattStatus?: number | null; + attErrorCode?: number | null; + }) { + super(errorInfo.message); + this.name = 'BleError'; + this.code = errorInfo.code as BleErrorCode; + this.isRetryable = errorInfo.isRetryable; + this.platform = errorInfo.platform as 'android' | 'ios'; + this.deviceId = errorInfo.deviceId ?? undefined; + this.serviceUUID = errorInfo.serviceUuid ?? undefined; + this.characteristicUUID = errorInfo.characteristicUuid ?? undefined; + this.operation = errorInfo.operation ?? undefined; + this.nativeDomain = errorInfo.nativeDomain ?? undefined; + this.nativeCode = errorInfo.nativeCode ?? undefined; + this.gattStatus = errorInfo.gattStatus ?? undefined; + this.attErrorCode = errorInfo.attErrorCode ?? undefined; + } +} +``` + +- [ ] **Step 3: Write tests for BleError** + +```typescript +// __tests__/BleError.test.ts +import { BleError, BleErrorCode } from '../src/BleError'; + +test('BleError constructs with all fields', () => { + const err = new BleError({ + code: BleErrorCode.ConnectionFailed, + message: 'GATT error 133', + isRetryable: true, + platform: 'android', + gattStatus: 133, + deviceId: 'AA:BB:CC:DD:EE:FF', + operation: 'connect', + nativeDomain: null, nativeCode: null, + serviceUuid: null, characteristicUuid: null, attErrorCode: null, + }); + expect(err.code).toBe(BleErrorCode.ConnectionFailed); + expect(err.isRetryable).toBe(true); + expect(err.gattStatus).toBe(133); + expect(err.deviceId).toBe('AA:BB:CC:DD:EE:FF'); + expect(err instanceof Error).toBe(true); +}); + +test('BleErrorCode values are correct', () => { + expect(BleErrorCode.DeviceNotFound).toBe(0); + expect(BleErrorCode.ManagerNotInitialized).toBe(400); + expect(BleErrorCode.UnknownError).toBe(999); +}); +``` + +- [ ] **Step 4: Run tests** + +```bash +npx jest __tests__/BleError.test.ts +``` + +- [ ] **Step 5: Commit** + +```bash +git add src/types.ts src/BleError.ts __tests__/BleError.test.ts +git commit -m "feat: TypeScript types (const objects) and unified error model" +``` + +--- + +### Task 3: Event batcher + +**Files:** +- Create: `src/EventBatcher.ts` +- Create: `__tests__/EventBatcher.test.ts` + +- [ ] **Step 1: Write failing tests for EventBatcher** + +```typescript +// __tests__/EventBatcher.test.ts +import { EventBatcher } from '../src/EventBatcher'; + +test('immediate mode delivers events without delay', () => { + const received: number[] = []; + const batcher = new EventBatcher(0, 50, (batch) => { + received.push(...batch); + }); + batcher.push(1); + batcher.push(2); + expect(received).toEqual([1, 2]); + batcher.dispose(); +}); + +test('batched mode collects events and delivers on interval', (done) => { + const received: number[][] = []; + const batcher = new EventBatcher(50, 50, (batch) => { + received.push([...batch]); + if (received.length === 1) { + expect(received[0]).toEqual([1, 2, 3]); + batcher.dispose(); + done(); + } + }); + batcher.push(1); + batcher.push(2); + batcher.push(3); + // Events should not be delivered yet + expect(received).toEqual([]); +}); + +test('max batch size caps delivery', (done) => { + const received: number[][] = []; + const batcher = new EventBatcher(50, 3, (batch) => { + received.push([...batch]); + if (received.length === 1) { + expect(received[0].length).toBeLessThanOrEqual(3); + batcher.dispose(); + done(); + } + }); + for (let i = 0; i < 10; i++) batcher.push(i); +}); +``` + +- [ ] **Step 2: Implement EventBatcher** + +```typescript +// src/EventBatcher.ts +export class EventBatcher { + private buffer: T[] = []; + private timer: ReturnType | null = null; + private disposed = false; + + constructor( + private intervalMs: number, + private maxBatchSize: number, + private onBatch: (events: T[]) => void, + ) { + if (intervalMs > 0) { + this.timer = setInterval(() => this.flush(), intervalMs); + } + } + + push(event: T): void { + if (this.disposed) return; + if (this.intervalMs === 0) { + this.onBatch([event]); + return; + } + this.buffer.push(event); + if (this.buffer.length >= this.maxBatchSize) { + this.flush(); + } + } + + flush(): void { + if (this.buffer.length === 0) return; + const batch = this.buffer.splice(0, this.maxBatchSize); + this.onBatch(batch); + } + + dispose(): void { + this.disposed = true; + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + this.flush(); + } +} +``` + +- [ ] **Step 3: Run tests** + +```bash +npx jest __tests__/EventBatcher.test.ts +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/EventBatcher.ts __tests__/EventBatcher.test.ts +git commit -m "feat: EventBatcher with configurable interval, max batch size, and immediate mode" +``` + +--- + +## Chunk 2: BleManager API + Device/Characteristic Wrappers + +### Task 4: BleManager class + +**Files:** +- Create: `src/BleManager.ts` + +- [ ] **Step 1: Create BleManager wrapping the native module** + +The BleManager class: +- Gets the native module via `TurboModuleRegistry.get` +- Wraps every native method with proper TypeScript types +- Converts native error events into `BleError` instances +- Manages scan/monitor subscriptions with proper cleanup +- Creates `EventBatcher` instances per-characteristic for notification batching +- Provides `startDeviceScan()` with callback pattern (wraps EventEmitter internally) +- Provides `monitorCharacteristicForDevice()` returning a `Subscription` with working `.remove()` +- Handles `transactionId` generation for cancellation + +Key methods: `createClient`, `destroyClient`, `state`, `startDeviceScan`, `stopDeviceScan`, `connectToDevice`, `cancelDeviceConnection`, `discoverAllServicesAndCharacteristics`, `readCharacteristicForDevice`, `writeCharacteristicForDevice`, `monitorCharacteristicForDevice`, `requestMTUForDevice`, `requestConnectionPriority`, `cancelTransaction`, `onDeviceDisconnected`, `onStateChange` + +- [ ] **Step 2: Commit** + +```bash +git add src/BleManager.ts +git commit -m "feat: BleManager API wrapping TurboModule with subscription management" +``` + +--- + +### Task 5: Device, Service, Characteristic, Descriptor wrappers + +**Files:** +- Create: `src/Device.ts` +- Create: `src/Service.ts` +- Create: `src/Characteristic.ts` +- Create: `src/Descriptor.ts` + +- [ ] **Step 1: Create wrapper classes** + +Each class wraps the native data and provides convenience methods that delegate back to BleManager. Device has `connect()`, `discoverAllServicesAndCharacteristics()`, `services()`, etc. Characteristic has `read()`, `write()`, `monitor()`. + +- [ ] **Step 2: Create index.ts exporting all public API** + +```typescript +// src/index.ts +export { BleManager } from './BleManager'; +export { Device } from './Device'; +export { Service } from './Service'; +export { Characteristic } from './Characteristic'; +export { Descriptor } from './Descriptor'; +export { BleError, BleErrorCode } from './BleError'; +export { State, LogLevel, ConnectionPriority, ConnectionState } from './types'; +export type { ScanOptions, ConnectOptions } from './types'; +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/Device.ts src/Service.ts src/Characteristic.ts src/Descriptor.ts src/index.ts +git commit -m "feat: Device/Service/Characteristic/Descriptor wrappers and public exports" +``` + +--- + +### Task 6: BleManager unit tests + +**Files:** +- Create: `__tests__/BleManager.test.ts` + +- [ ] **Step 1: Write tests with mocked native module** + +Test: scan starts/stops correctly, subscription cleanup works, monitor `.remove()` cleans up both native and JS listener, error events become `BleError` instances, `transactionId` generation and cancellation. + +- [ ] **Step 2: Run tests** + +```bash +npx jest __tests__/BleManager.test.ts +``` + +- [ ] **Step 3: Commit** + +```bash +git add __tests__/BleManager.test.ts +git commit -m "test: BleManager unit tests with mocked native module" +``` + +--- + +### Task 7: Update Expo config plugin + +**Files:** +- Modify: `plugin/src/withBLEAndroidManifest.ts` +- Modify: `plugin/src/withBluetoothPermissions.ts` + +- [ ] **Step 1: Update Android manifest for namespace mode** + +Fix the empty `AndroidManifestNew.xml` issue. Ensure BLE permissions are declared correctly regardless of AGP version. Add `neverForLocation` flag handling. + +- [ ] **Step 2: Commit** + +```bash +git add plugin/ +git commit -m "fix: Expo plugin — proper manifest for AGP namespace mode, neverForLocation flag" +``` + +--- + +### Task 8: Package.json and build config updates + +**Files:** +- Modify: `package.json` +- Modify: `tsconfig.json` + +- [ ] **Step 1: Update dependencies** + +- Remove `@types/react-native` (deprecated since RN 0.71) +- Update peer dependencies to `react-native >= 0.82` +- Update ESLint toolchain +- Add `codegenConfig` +- Remove `includesGeneratedCode` if present + +- [ ] **Step 2: Commit** + +```bash +git add package.json tsconfig.json +git commit -m "chore: update deps, add codegenConfig, require RN 0.82+" +``` diff --git a/docs/plans/2026-03-15-plan-b-android-native.md b/docs/plans/2026-03-15-plan-b-android-native.md new file mode 100644 index 000000000..ccb4da3af --- /dev/null +++ b/docs/plans/2026-03-15-plan-b-android-native.md @@ -0,0 +1,255 @@ +# Plan B: Android Native — Kotlin TurboModule + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement the Android native layer as a Kotlin TurboModule using Nordic Android-BLE-Library for GATT operations and Scanner Compat for scanning. + +**Architecture:** `BlePlxModule.kt` extends the Codegen-generated `NativeBlePlxSpec`. Uses `ConcurrentHashMap` for thread-safe state. Coroutine scope per module instance, cancelled on `invalidate()`. Per-device `BleManagerWrapper` extends Nordic's `BleManager`. `ScanManager` wraps Scanner Compat with built-in scan throttle debouncing. + +**Tech Stack:** Kotlin, Nordic Android-BLE-Library 2.11.0, Nordic Scanner Compat 1.6.0, Kotlin Coroutines, JUnit 5, MockK + +**Spec:** `docs/specs/2026-03-15-v4-turbomodule-rewrite.md` (Sections 2, 6) + +**Depends on:** Plan A (Codegen spec must exist) + +--- + +## File Structure + +``` +android/src/main/kotlin/com/bleplx/ +├── BlePlxModule.kt ← TurboModule entry point (extends NativeBlePlxSpec) +├── BlePlxPackage.kt ← ReactPackage registration +├── BleManagerWrapper.kt ← Per-device Nordic BleManager subclass +├── ScanManager.kt ← Scanner Compat + throttle debouncing +├── PermissionHelper.kt ← Runtime permission checks (API 31+) +├── EventSerializer.kt ← Native objects → Codegen event types +├── ErrorConverter.kt ← Android errors → unified BleErrorCode +└── L2CAPManager.kt ← L2CAP support (stub for Android — iOS only) +android/src/test/kotlin/com/bleplx/ +├── ScanManagerTest.kt +├── ErrorConverterTest.kt +├── PermissionHelperTest.kt +└── EventSerializerTest.kt +``` + +--- + +## Chunk 1: Module scaffolding + permissions + +### Task 1: Kotlin TurboModule entry point + +**Files:** +- Create: `android/src/main/kotlin/com/bleplx/BlePlxModule.kt` +- Create: `android/src/main/kotlin/com/bleplx/BlePlxPackage.kt` + +- [ ] **Step 1: Create BlePlxModule extending NativeBlePlxSpec** + +```kotlin +class BlePlxModule(reactContext: ReactApplicationContext) + : NativeBlePlxSpec(reactContext) { + + companion object { + const val NAME = "NativeBlePlx" + } + + private val moduleScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) + private val connectedManagers = ConcurrentHashMap() + private var scanManager: ScanManager? = null + + override fun getName(): String = NAME + + override fun invalidate() { + super.invalidate() + moduleScope.cancel() + scanManager?.stopScan() + connectedManagers.values.forEach { it.close() } + connectedManagers.clear() + } + + // ... method stubs for all spec methods +} +``` + +- [ ] **Step 2: Create BlePlxPackage for module registration** + +- [ ] **Step 3: Update build.gradle with Nordic dependencies** + +```groovy +dependencies { + implementation 'com.facebook.react:react-android' + implementation 'no.nordicsemi.android:ble:2.11.0' + implementation 'no.nordicsemi.android:ble-ktx:2.11.0' + implementation 'no.nordicsemi.android.support.v18:scanner:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' +} +``` + +- [ ] **Step 4: Commit** + +```bash +git add android/ +git commit -m "feat(android): Kotlin TurboModule scaffolding with Nordic dependencies" +``` + +--- + +### Task 2: Permission helper + +**Files:** +- Create: `android/src/main/kotlin/com/bleplx/PermissionHelper.kt` +- Create: `android/src/test/kotlin/com/bleplx/PermissionHelperTest.kt` + +- [ ] **Step 1: Write tests for permission checks** + +Test: API < 31 requires BLUETOOTH + location, API 31+ requires BLUETOOTH_SCAN/CONNECT, missing permission throws correct BleError. + +- [ ] **Step 2: Implement PermissionHelper** + +Runtime checks for `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT`, `ACCESS_FINE_LOCATION` with proper SDK version gating. Throws `BleError` with correct error code when permissions missing. + +- [ ] **Step 3: Run tests, commit** + +--- + +### Task 3: Error converter + +**Files:** +- Create: `android/src/main/kotlin/com/bleplx/ErrorConverter.kt` + +- [ ] **Step 1: Map Android GATT errors → unified BleErrorCode** + +Maps: GATT status codes (0, 8, 19, 22, 133, 257), scan error codes (1-6), SecurityException, Nordic-specific errors → BleErrorCode. Includes `isRetryable` logic (133 = retryable, 19 = not). + +- [ ] **Step 2: Test and commit** + +--- + +## Chunk 2: Scanning + Connection + +### Task 4: Scan manager with throttle debouncing + +**Files:** +- Create: `android/src/main/kotlin/com/bleplx/ScanManager.kt` + +- [ ] **Step 1: Implement ScanManager** + +- Wraps Nordic Scanner Compat +- Tracks scan start timestamps (last 5) +- Rejects start if would exceed 5 starts in 30s window (returns ScanThrottled error) +- Spaces restarts by ≥6 seconds +- Emits results via callback +- Handles `legacyScan` flag for BLE 5.0 extended advertising + +- [ ] **Step 2: Test throttle logic** + +- [ ] **Step 3: Wire into BlePlxModule startDeviceScan/stopDeviceScan** + +- [ ] **Step 4: Commit** + +--- + +### Task 5: BleManagerWrapper (per-device GATT) + +**Files:** +- Create: `android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt` + +- [ ] **Step 1: Implement Nordic BleManager subclass** + +```kotlin +class BleManagerWrapper(context: Context) : BleManager(context) { + private val characteristics = ConcurrentHashMap() + + override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { + // Cache all discovered characteristics + gatt.services.forEach { service -> + service.characteristics.forEach { char -> + characteristics["${service.uuid}/${char.uuid}"] = char + } + } + return true // Accept any device + } + + override fun initialize() { + // Auto-MTU on Android < 34 (Android 14+ auto-negotiates 517) + if (Build.VERSION.SDK_INT < 34) { + requestMtu(517).enqueue() + } + } + + override fun onServicesInvalidated() { + characteristics.clear() + } + + // Expose suspend functions for read/write/monitor + suspend fun readCharacteristic(serviceUuid: String, charUuid: String): ByteArray { ... } + suspend fun writeCharacteristic(serviceUuid: String, charUuid: String, data: ByteArray, withResponse: Boolean) { ... } + fun monitorCharacteristic(serviceUuid: String, charUuid: String): Flow { ... } +} +``` + +- [ ] **Step 2: Wire connect/disconnect into BlePlxModule** + +Connection with retry: `manager.connect(device).retry(retries, retryDelay).timeout(timeout).suspend()` + +- [ ] **Step 3: Commit** + +--- + +### Task 6: Event serializer + +**Files:** +- Create: `android/src/main/kotlin/com/bleplx/EventSerializer.kt` + +Convert native objects (BluetoothDevice, ScanResult, BluetoothGattCharacteristic) → Codegen typed event structs for emission via `emitOnScanResult`, `emitOnConnectionStateChange`, etc. + +- [ ] **Step 1: Implement all serializers** +- [ ] **Step 2: Test and commit** + +--- + +## Chunk 3: Read/Write/Monitor + PHY + Bonding + +### Task 7: GATT operations in BlePlxModule + +- [ ] **Step 1: Implement readCharacteristic, writeCharacteristic** + +Both delegate to `BleManagerWrapper` suspend functions, convert results to Codegen return types. + +- [ ] **Step 2: Implement monitorCharacteristic** + +Creates a coroutine collecting from `BleManagerWrapper.monitorCharacteristic()` Flow, emits via `emitOnCharacteristicValueUpdate`. Tracked by `transactionId` for cancellation. Properly cleans up on cancel/disconnect. + +- [ ] **Step 3: Implement requestMtu, requestConnectionPriority, requestPhy, readPhy** + +- [ ] **Step 4: Implement getBondedDevices** + +Uses `BluetoothAdapter.getBondedDevices()`. Note: calls `ensureBond()` after connection for encryption verification, not just `getBondState()`. + +- [ ] **Step 5: Commit** + +--- + +### Task 8: Android manifest + final wiring + +**Files:** +- Modify: `android/src/main/AndroidManifest.xml` +- Create: `android/src/main/AndroidManifestNew.xml` (non-empty!) + +- [ ] **Step 1: Fix manifests** + +AndroidManifest.xml: proper permissions with `maxSdkVersion`, `neverForLocation` flag. +AndroidManifestNew.xml: same permissions (NOT empty — the v3 bug). + +- [ ] **Step 2: Integration test — verify module loads** + +```bash +cd example && npx react-native run-android +``` + +- [ ] **Step 3: Commit** + +```bash +git add android/ +git commit -m "feat(android): complete Kotlin TurboModule with Nordic BLE Library" +``` diff --git a/docs/plans/2026-03-15-plan-c-ios-native.md b/docs/plans/2026-03-15-plan-c-ios-native.md new file mode 100644 index 000000000..c79b401fa --- /dev/null +++ b/docs/plans/2026-03-15-plan-c-ios-native.md @@ -0,0 +1,359 @@ +# Plan C: iOS Native — Swift TurboModule + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement the iOS native layer as a Swift TurboModule using direct CoreBluetooth with async/await actors and proper GATT operation queuing. + +**Architecture:** ObjC++ entry point (`BlePlx.mm`) conforms to Codegen-generated spec, delegates to `@objc BLEModuleImpl` Swift class, which owns a `BLEActor` (Swift actor with custom executor on CoreBluetooth's DispatchQueue). Per-peripheral `PeripheralWrapper` actors handle GATT operation serialization. State restoration bootstraps eagerly on background relaunch. + +**Tech Stack:** Swift 5.9+, CoreBluetooth, Swift Concurrency (actors, async/await, AsyncStream), ObjC++, XCTest + +**Spec:** `docs/specs/2026-03-15-v4-turbomodule-rewrite.md` (Sections 3, 7) + +**Depends on:** Plan A (Codegen spec must exist) + +--- + +## File Structure + +``` +ios/ +├── BlePlx.mm ← ObjC++ TurboModule entry (Codegen base class) +├── BlePlx-Bridging-Header.h ← Swift/ObjC bridge +├── BLEModuleImpl.swift ← @objc Swift class, delegates to actor +├── BLEActor.swift ← Central manager actor (custom executor) +├── PeripheralWrapper.swift ← Per-peripheral GATT queue actor +├── GATTOperationQueue.swift ← Serial operation queue with timeouts +├── CentralManagerDelegate.swift ← CB delegate → async bridge +├── PeripheralDelegate.swift ← CB peripheral delegate → async bridge +├── ScanManager.swift ← Scan with AsyncStream +├── StateRestoration.swift ← Background restoration handler +├── EventSerializer.swift ← CB objects → Codegen event types +├── ErrorConverter.swift ← CB errors → unified BleErrorCode +├── L2CAPManager.swift ← L2CAP channel lifecycle +ios/Tests/ +├── GATTOperationQueueTests.swift +├── ErrorConverterTests.swift +├── EventSerializerTests.swift +``` + +--- + +## Chunk 1: ObjC++ entry + Swift actor scaffolding + +### Task 1: ObjC++ TurboModule entry point + +**Files:** +- Create: `ios/BlePlx.mm` +- Create: `ios/BlePlx-Bridging-Header.h` + +- [ ] **Step 1: Create .mm file conforming to generated spec** + +The `.mm` file: +- Imports Codegen-generated header +- Extends `NativeBlePlxSpec` (generated ObjC++ base class) +- Delegates every method to `BLEModuleImpl` (Swift, via `@objc`) +- Registers as `NativeBlePlx` module name + +```objc +// BlePlx.mm +#import +#import "react_native_ble_plx-Swift.h" + +@interface BlePlx : NativeBlePlxSpec +@property (nonatomic, strong) BLEModuleImpl *impl; +@end + +@implementation BlePlx + +RCT_EXPORT_MODULE(NativeBlePlx) + +- (instancetype)init { + self = [super init]; + if (self) { + _impl = [[BLEModuleImpl alloc] init]; + } + return self; +} + +- (void)createClient:(NSString *)restoreStateIdentifier + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl createClient:restoreStateIdentifier resolve:resolve reject:reject]; +} + +// ... delegate all methods to _impl + +- (void)invalidate { + [_impl invalidate]; + [super invalidate]; // Fix audit #25 — call super +} + +@end +``` + +- [ ] **Step 2: Commit** + +```bash +git add ios/BlePlx.mm ios/BlePlx-Bridging-Header.h +git commit -m "feat(ios): ObjC++ TurboModule entry point" +``` + +--- + +### Task 2: BLEModuleImpl + BLEActor scaffolding + +**Files:** +- Create: `ios/BLEModuleImpl.swift` +- Create: `ios/BLEActor.swift` + +- [ ] **Step 1: Create BLEModuleImpl (@objc, delegates to actor)** + +```swift +@objc public class BLEModuleImpl: NSObject { + private var actor: BLEActor? + private var emitter: ((String, Any) -> Void)? + + @objc public func setEventEmitter(_ emitter: @escaping (String, Any) -> Void) { + self.emitter = emitter + } + + @objc public func createClient(_ restoreId: String?, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + // Invalidate previous if exists + actor?.invalidate() + actor = BLEActor(restoreId: restoreId) + resolve(nil) + } + + @objc public func invalidate() { + actor?.invalidate() + actor = nil + } +} +``` + +- [ ] **Step 2: Create BLEActor with custom executor** + +```swift +@preconcurrency import CoreBluetooth + +actor BLEActor { + let queue = DispatchQueue(label: "com.bleplx.ble") + + nonisolated var unownedExecutor: UnownedSerialExecutor { + queue.asUnownedSerialExecutor() + } + + private var centralManager: CBCentralManager! + private let delegateHandler: CentralManagerDelegate + private var peripherals: [UUID: PeripheralWrapper] = [:] + + init(restoreId: String?) { + delegateHandler = CentralManagerDelegate() + let options: [String: Any] = restoreId != nil + ? [CBCentralManagerOptionRestoreIdentifierKey: restoreId!] + : [:] + centralManager = CBCentralManager( + delegate: delegateHandler, queue: queue, options: options + ) + } + + func invalidate() { + centralManager.stopScan() + peripherals.values.forEach { $0.disconnect() } + peripherals.removeAll() + } +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add ios/BLEModuleImpl.swift ios/BLEActor.swift +git commit -m "feat(ios): BLEModuleImpl + BLEActor with custom executor on CB queue" +``` + +--- + +### Task 3: Delegate-to-async bridges + +**Files:** +- Create: `ios/CentralManagerDelegate.swift` +- Create: `ios/PeripheralDelegate.swift` + +- [ ] **Step 1: CentralManagerDelegate — bridges CB delegate → async** + +Uses `AsyncStream` for scan results, `CheckedContinuation` for connection, state change subjects. Handles `willRestoreState` with bootstrap timing (fires before `didUpdateState`). Implements `centralManager(_:connectionEventDidOccurForPeripheral:)` (iOS 13+) and emits `onConnectionEvent`. + +- [ ] **Step 2: PeripheralDelegate — bridges peripheral delegate → async** + +`CheckedContinuation` for: service discovery, characteristic read, characteristic write (withResponse), descriptor operations. `AsyncStream` for characteristic notifications. Flow control for `peripheralIsReady(toSendWriteWithoutResponse:)`. + +- [ ] **Step 3: Commit** + +--- + +## Chunk 2: GATT operation queue + scanning + +### Task 4: GATT operation queue + +**Files:** +- Create: `ios/GATTOperationQueue.swift` +- Create: `ios/Tests/GATTOperationQueueTests.swift` + +- [ ] **Step 1: Write failing tests** + +Test: operations execute serially, timeout fires after configured interval, concurrent enqueue waits for previous to complete, cancel stops pending operations. + +- [ ] **Step 2: Implement GATTOperationQueue actor** + +```swift +actor GATTOperationQueue { + private var pending: [QueuedOperation] = [] + private var isExecuting = false + private let timeoutSeconds: TimeInterval = 10.0 + + func enqueue(_ operation: @escaping () -> Void) async throws -> Data? { + return try await withCheckedThrowingContinuation { continuation in + let op = QueuedOperation(work: operation, continuation: continuation) + pending.append(op) + if !isExecuting { executeNext() } + } + } + + func operationCompleted(result: Result) { + // Resume current continuation with result, then executeNext() + isExecuting = false + executeNext() + } + + private func executeNext() { ... } +} +``` + +- [ ] **Step 3: Run tests, commit** + +--- + +### Task 5: Peripheral wrapper with GATT queue + +**Files:** +- Create: `ios/PeripheralWrapper.swift` + +- [ ] **Step 1: Implement PeripheralWrapper actor** + +Per-peripheral actor that: +- Owns `CBPeripheral` reference (safe — same queue via custom executor) +- Owns `PeripheralDelegate` (weak back-reference to avoid retain cycle) +- Owns `GATTOperationQueue` +- Exposes: `discoverServices()`, `readCharacteristic()`, `writeCharacteristic()`, `monitorCharacteristic()` (returns `AsyncStream`) +- Handles `writeWithoutResponse` flow control via `canSendWriteWithoutResponse` + +- [ ] **Step 2: Commit** + +--- + +### Task 6: Scan manager + +**Files:** +- Create: `ios/ScanManager.swift` + +- [ ] **Step 1: Implement scan with AsyncStream** + +Wraps `CBCentralManager.scanForPeripherals`. Returns `AsyncStream`. Handles background mode constraint (must have service UUIDs when backgrounded). Properly stops scan on `AsyncStream` termination. + +- [ ] **Step 2: Commit** + +--- + +## Chunk 3: State restoration + L2CAP + final wiring + +### Task 7: State restoration + +**Files:** +- Create: `ios/StateRestoration.swift` + +- [ ] **Step 1: Implement restoration handler** + +- Handles `willRestoreState` delegate callback (fires BEFORE `didUpdateState`) +- Re-attaches delegates to restored peripherals +- Stores peripheral references strongly (CB does not retain them) +- Buffers restoration data until JS subscribes via `onRestoreState` +- Bootstrap: native module eagerly creates `CBCentralManager` on background relaunch during `application(_:willFinishLaunchingWithOptions:)`, before JS bridge is ready + +- [ ] **Step 2: Commit** + +--- + +### Task 8: L2CAP manager + +**Files:** +- Create: `ios/L2CAPManager.swift` + +- [ ] **Step 1: Implement L2CAP channel lifecycle** + +- `openChannel(peripheral, psm)` → returns `L2CAPChannelWrapper` +- `L2CAPChannelWrapper` owns channel + streams, strongly retains both +- Schedules `NSInputStream`/`NSOutputStream` on correct RunLoop +- `StreamDelegate` → `AsyncStream` bridge for incoming data +- Write method with flow control +- Close method cleans up streams and channel +- Documents: L2CAP does NOT wake suspended apps + +- [ ] **Step 2: Commit** + +--- + +### Task 9: Error converter + event serializer + +**Files:** +- Create: `ios/ErrorConverter.swift` +- Create: `ios/EventSerializer.swift` + +- [ ] **Step 1: Map CBError → BleErrorCode** + +CBError codes (.invalidParameters, .peerRemovedPairingInformation, etc.) → unified codes. CBATTError codes → `attErrorCode`. Includes `isRetryable` logic. + +- [ ] **Step 2: Serialize CB objects → Codegen event types** + +Extract Sendable values (UUID strings, Data as Base64, booleans) from CB objects. Never pass `CBPeripheral` or `CBCharacteristic` across actor boundaries. + +- [ ] **Step 3: Test and commit** + +--- + +### Task 10: Wire everything into BLEModuleImpl + +- [ ] **Step 1: Implement all method delegations from BLEModuleImpl → BLEActor** + +Each `@objc` method launches a Task, calls into the actor, converts results, and calls resolve/reject. + +- [ ] **Step 2: Implement all event emissions** + +Scan results, connection state changes, characteristic values, state changes, restoration events, **L2CAP data/close events**, and **connection events (iOS 13+)** → all flow through the event emitter set by the `.mm` file. Specifically: +- Wire `L2CAPManager` AsyncStream → `emitOnL2CAPData` / `emitOnL2CAPClose` +- Wire `CentralManagerDelegate.connectionEvent` → `emitOnConnectionEvent` + +- [ ] **Step 3: Update podspec** + +```ruby +s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" +s.private_header_files = "ios/**/*.h" +s.platforms = { :ios => "14.0" } +install_modules_dependencies(s) +``` + +- [ ] **Step 4: Build test on iOS simulator** + +```bash +cd example && npx react-native run-ios +``` + +- [ ] **Step 5: Commit** + +```bash +git add ios/ +git commit -m "feat(ios): complete Swift TurboModule with actor-based CoreBluetooth" +``` diff --git a/docs/plans/2026-03-15-plan-d-testing.md b/docs/plans/2026-03-15-plan-d-testing.md new file mode 100644 index 000000000..f514f2a7d --- /dev/null +++ b/docs/plans/2026-03-15-plan-d-testing.md @@ -0,0 +1,227 @@ +# Plan D: Testing — Unit, Simulated Integration, Hardware + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Comprehensive test suite with CI-friendly simulated tests and a pre-release hardware test framework. + +**Architecture:** Three test tiers: (1) Unit tests per platform (Jest/JUnit/XCTest) run on every PR. (2) Simulated integration tests with mock GATT peripherals run in CI. (3) Hardware integration tests with real BLE devices run pre-release. + +**Tech Stack:** Jest, JUnit 5, MockK, XCTest, Nordic CoreBluetoothMock (iOS), Robolectric (Android), Maestro/Detox (hardware) + +**Spec:** `docs/specs/2026-03-15-v4-turbomodule-rewrite.md` (Section 7) + +**Depends on:** Plans A, B, C (all code must exist) + +--- + +## File Structure + +``` +__tests__/ ← JS unit tests (Jest) +├── BleManager.test.ts +├── EventBatcher.test.ts +├── BleError.test.ts +├── protocol.test.ts +android/src/test/kotlin/com/bleplx/ ← Android unit tests (JUnit 5) +├── ScanManagerTest.kt +├── ErrorConverterTest.kt +├── PermissionHelperTest.kt +├── EventSerializerTest.kt +├── BleManagerWrapperTest.kt +ios/Tests/ ← iOS unit tests (XCTest) +├── GATTOperationQueueTests.swift +├── ErrorConverterTests.swift +├── EventSerializerTests.swift +├── StateRestorationTests.swift +integration-tests/ +├── simulated/ ← Mock GATT peripheral tests (CI) +│ ├── ios/ +│ │ ├── MockPeripheral.swift ← CoreBluetoothMock-based fake peripheral +│ │ └── SimulatedBLETests.swift +│ ├── android/ +│ │ ├── MockGattServer.kt ← Custom mock GATT +│ │ └── SimulatedBLETests.kt +│ └── js/ +│ └── fullSync.test.ts ← End-to-end JS flow with mocked native +├── hardware/ ← Dual-device tests (pre-release) +│ ├── peripheral-firmware/ ← nRF52840 test firmware +│ ├── test-app/ ← Phone test app +│ └── maestro/ ← Maestro flows +│ ├── scan-pair-sync.yaml +│ ├── disconnect-recovery.yaml +│ └── indicate-stress.yaml +``` + +--- + +## Chunk 1: JS Unit Tests + +### Task 1: Protocol parsing tests + +**Files:** +- Create: `__tests__/protocol.test.ts` + +- [ ] **Step 1: Write tests for Base64 encode/decode, DeviceInfo parsing, CharacteristicInfo parsing, error event conversion** + +Key scenarios: valid data, truncated data, null fields, unknown event types, 0xFFFFFFFF timestamp sentinel. + +- [ ] **Step 2: Run tests, commit** + +--- + +### Task 2: Full JS flow tests with mocked native + +**Files:** +- Create: `integration-tests/simulated/js/fullSync.test.ts` + +- [ ] **Step 1: Mock the TurboModule and test** + +Mock `NativeBlePlx` with in-memory state. Test the full flow: +1. `createClient()` → state = PoweredOn +2. `startDeviceScan()` → onScanResult fires with mock device +3. `connectToDevice()` → resolves with DeviceInfo +4. `writeCharacteristic()` → resolves with value +5. `monitorCharacteristic()` → onCharacteristicValueUpdate fires +6. Subscription `.remove()` → stops native monitor AND JS listener (audit #3 fix) +7. `cancelDeviceConnection()` → onConnectionStateChange fires +8. Error scenarios: permission denied, connection timeout, GATT error + +- [ ] **Step 2: Run tests, commit** + +--- + +## Chunk 2: Android Native Tests + +### Task 3: Android unit tests + +**Files:** +- Multiple test files in `android/src/test/kotlin/com/bleplx/` + +- [ ] **Step 1: ScanManagerTest — throttle logic** + +Test: 5 scans in 30s → 6th blocked with ScanThrottled. Scans spaced by 6s succeed. Stop/start cycle counts correctly. + +- [ ] **Step 2: ErrorConverterTest — GATT error mapping** + +Test: GATT 133 → ConnectionFailed + isRetryable=true. GATT 19 → DeviceDisconnected + isRetryable=false. SecurityException → ConnectPermissionDenied. ScanCallback error codes 1-6. + +- [ ] **Step 3: PermissionHelperTest — SDK version gating** + +Test: API 30 requires BLUETOOTH + location. API 31+ requires BLUETOOTH_SCAN + BLUETOOTH_CONNECT. Missing permission → correct error code. + +- [ ] **Step 4: BleManagerWrapperTest — MTU auto-negotiation** + +Test: API < 34 → requestMtu(517) called. API >= 34 → requestMtu NOT called (system auto-negotiates). + +- [ ] **Step 5: Run all tests, commit** + +```bash +cd android && ./gradlew test +``` + +--- + +### Task 4: Android simulated integration tests + +**Files:** +- Create: `integration-tests/simulated/android/MockGattServer.kt` +- Create: `integration-tests/simulated/android/SimulatedBLETests.kt` + +- [ ] **Step 1: Create MockGattServer using Robolectric shadows** + +Simulates: service discovery, characteristic read/write, notification/indication, MTU negotiation, disconnect. Configurable responses and error injection. + +- [ ] **Step 2: Write integration tests** + +Full connect → discover → read → write → monitor → disconnect cycle. Indicate-based streaming (Smart Cap Rx protocol). Watermark ACK flow. Error injection: disconnect mid-transfer, timeout, permission revoked. Concurrent Write + Indicate stress test (Gemini finding). + +- [ ] **Step 3: Run tests, commit** + +--- + +## Chunk 3: iOS Native Tests + +### Task 5: iOS unit tests + +**Files:** +- Multiple test files in `ios/Tests/` + +- [ ] **Step 1: GATTOperationQueueTests** + +Test: operations execute serially, timeout fires, concurrent enqueue waits, cancel stops pending. + +- [ ] **Step 2: ErrorConverterTests — CBError mapping** + +Test: CBError.connectionFailed → ConnectionFailed. CBATTError → attErrorCode mapping. CBError.peerRemovedPairingInformation → BondLost. + +- [ ] **Step 3: StateRestorationTests** + +Test: `willRestoreState` fires before `didUpdateState` — verify buffering works. Restored peripherals re-attach delegates. Background relaunch bootstrap path. + +- [ ] **Step 4: Run tests, commit** + +```bash +cd ios && xcodebuild test -scheme BlePlx-Tests +``` + +--- + +### Task 6: iOS simulated integration tests + +**Files:** +- Create: `integration-tests/simulated/ios/MockPeripheral.swift` +- Create: `integration-tests/simulated/ios/SimulatedBLETests.swift` + +- [ ] **Step 1: Create MockPeripheral using Nordic CoreBluetoothMock** + +`pod 'CoreBluetoothMock'` — provides `CBMCentralManagerMock` and `CBMPeripheralSpec` for simulating GATT peripherals without real Bluetooth. + +- [ ] **Step 2: Write integration tests** + +Same scenarios as Android: full lifecycle, Smart Cap Rx protocol, error injection, GATT queue stress, L2CAP channel lifecycle. Plus iOS-specific: state restoration after simulated background kill, `canSendWriteWithoutResponse` flow control, `CBManagerAuthorization` changes mid-scan. + +- [ ] **Step 3: Run tests, commit** + +--- + +## Chunk 4: Hardware Integration Tests + +### Task 7: Test peripheral firmware + +**Files:** +- Create: `integration-tests/hardware/peripheral-firmware/` + +- [ ] **Step 1: Create nRF52840 test firmware** + +Arduino sketch (or Zephyr) for nRF52840 DK/XIAO that exposes: +- A test GATT service with read/write/notify/indicate characteristics +- Configurable MTU response +- Configurable connection parameters +- Smart Cap Rx protocol service (for protocol-level testing) +- Serial command interface for test control (trigger disconnect, change values, inject errors) + +- [ ] **Step 2: Commit** + +--- + +### Task 8: Maestro hardware test flows + +**Files:** +- Create: `integration-tests/hardware/maestro/*.yaml` + +- [ ] **Step 1: Create Maestro flows** + +- `scan-pair-sync.yaml`: scan → find test device → connect → sync time → transfer events → ACK → disconnect +- `disconnect-recovery.yaml`: connect → trigger remote disconnect → verify reconnection +- `indicate-stress.yaml`: connect → rapid indicate stream → verify no dropped packets → ACK + +- [ ] **Step 2: Document test setup** + +README with: required hardware (nRF52840 + phone), firmware flash instructions, Maestro install, how to run tests. + +- [ ] **Step 3: Commit** + +```bash +git add integration-tests/ +git commit -m "test: hardware integration framework with nRF52840 test firmware and Maestro flows" +``` diff --git a/docs/plans/2026-03-15-plan-e-cleanup-testing-integration.md b/docs/plans/2026-03-15-plan-e-cleanup-testing-integration.md new file mode 100644 index 000000000..38067d5c7 --- /dev/null +++ b/docs/plans/2026-03-15-plan-e-cleanup-testing-integration.md @@ -0,0 +1,314 @@ +# Plan E: v3 Cleanup, Example App, Build Verification, and Hardware Integration + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove all v3 code, create a working RN 0.82+ example/test app that builds on both platforms, write native unit tests, and create hardware integration test infrastructure. + +**Architecture:** Fresh RN 0.82+ app with `file:../` local dependency to trigger Codegen. Test firmware on nRF52840 XIAO with GATT test service. Maestro flows for E2E testing on physical phones. + +**Tech Stack:** React Native 0.82+, Jest, JUnit 5/MockK, XCTest, Arduino (nRF52840), Maestro + +**Depends on:** Plans A, B, C (all implemented) + +--- + +## File Structure + +``` +# Files to DELETE (v3 remnants) +src/BleError.js +src/BleManager.js +src/BleModule.js +src/Characteristic.js +src/Descriptor.js +src/Device.js +src/EventBatcher.js # (v3 JS version — v4 is .ts) +src/index.d.ts +src/index.js +src/Service.js +src/TypeDefinition.js +src/types.js # (v3 JS version — v4 is .ts) +src/Utils.js +__tests__/BleError.test.js +__tests__/BleManager.js +__tests__/BleManager.test.js # (old v3 — v4 is different file) +__tests__/Characteristic.js +__tests__/Descriptor.js +__tests__/Device.js +__tests__/EventBatcher.test.js # (old v3) +__tests__/Service.js +__tests__/Utils.js +.flowconfig + +# Files to CREATE/MODIFY +example/ # Fresh RN 0.82+ app (replace existing) +android/src/test/kotlin/ # JUnit tests +ios/Tests/ # XCTest tests +integration-tests/hardware/peripheral-firmware/ # Arduino test firmware +integration-tests/hardware/maestro/ # Updated Maestro flows +``` + +--- + +## Phase 1: v3 Cleanup + +### Task 1: Remove v3 source and config + +**Files:** +- Delete: all `src/*.js`, `src/index.d.ts` +- Delete: all `__tests__/*.js` +- Delete: `.flowconfig` +- Modify: `package.json` — remove Flow devDeps, update scripts +- Modify: `.eslintrc.json` — remove Flow/hermes overrides + +- [ ] **Step 1: Delete all v3 JS source files** + +```bash +rm src/BleError.js src/BleManager.js src/BleModule.js src/Characteristic.js \ + src/Descriptor.js src/Device.js src/EventBatcher.js src/index.d.ts \ + src/index.js src/Service.js src/TypeDefinition.js src/types.js src/Utils.js +``` + +- [ ] **Step 2: Delete all v3 JS test files** + +```bash +rm __tests__/BleError.test.js __tests__/BleManager.js __tests__/BleManager.test.js \ + __tests__/Characteristic.js __tests__/Descriptor.js __tests__/Device.js \ + __tests__/EventBatcher.test.js __tests__/Service.js __tests__/Utils.js +``` + +Note: Keep __tests__/*.test.ts (v4 tests). + +- [ ] **Step 3: Delete Flow config** + +```bash +rm .flowconfig +``` + +- [ ] **Step 4: Update package.json** + +Remove from devDependencies: +- `@babel/preset-flow` +- `eslint-plugin-flowtype` +- `eslint-plugin-ft-flow` +- `flow-bin` +- `hermes-eslint` +- `documentation` + +Update scripts: +- `lint`: remove `flow &&` and `documentation lint index.js` references +- `docs`: remove (was documentation build) +- Keep `test`, `build`, `typecheck` + +- [ ] **Step 5: Update .eslintrc.json** + +Remove the Flow/hermes-eslint override block entirely (the one matching `src/**/*.js`). +Remove Flow-related globals (`$Keys`, `$Values`). + +- [ ] **Step 6: Verify v4 tests still pass** + +```bash +npx jest --config jest.config.js __tests__/ +``` + +- [ ] **Step 7: Commit** + +```bash +git add -A +git commit -m "chore: remove all v3 JS/Flow source, tests, and config" +``` + +--- + +## Phase 2: Example App + Build Verification + +### Task 2: Create fresh RN 0.82+ example app + +**Files:** +- Replace: `example/` directory entirely + +- [ ] **Step 1: Remove old example app** + +```bash +rm -rf example/ +``` + +- [ ] **Step 2: Create fresh RN 0.82+ app** + +```bash +npx react-native@latest init BlePlxExample --directory example +``` + +- [ ] **Step 3: Wire local library dependency** + +In `example/package.json`: +```json +{ + "dependencies": { + "react-native-ble-plx": "file:../" + } +} +``` + +- [ ] **Step 4: Configure Android** + +Set `ANDROID_HOME=~/Library/Android/sdk` in `example/android/local.properties`: +``` +sdk.dir=/Users/shan/Library/Android/sdk +``` + +- [ ] **Step 5: Configure iOS** + +```bash +cd example/ios && pod install +``` + +- [ ] **Step 6: Commit scaffold** + +```bash +git commit -m "feat: fresh RN 0.82+ example app with local library dependency" +``` + +### Task 3: Build test screens + +**Files:** +- Create: `example/src/screens/ScanScreen.tsx` +- Create: `example/src/screens/DeviceScreen.tsx` +- Create: `example/src/screens/CharacteristicScreen.tsx` +- Create: `example/src/App.tsx` + +Screens: +1. **ScanScreen** — Start/stop scan, list discovered devices, tap to connect. testID: `scan-start`, `scan-stop`, `device-list`, `device-item-{id}` +2. **DeviceScreen** — Show services/characteristics after discovery, disconnect button. testID: `service-list`, `char-item-{uuid}`, `disconnect-btn` +3. **CharacteristicScreen** — Read value, write value, start/stop monitor. testID: `read-btn`, `write-input`, `write-btn`, `monitor-toggle`, `value-display` + +All screens use BleManager v4 API directly (not Device wrapper — test the raw API). + +- [ ] **Step 1: Create App with navigation** +- [ ] **Step 2: Create ScanScreen** +- [ ] **Step 3: Create DeviceScreen** +- [ ] **Step 4: Create CharacteristicScreen** +- [ ] **Step 5: Commit** + +### Task 4: Android build verification + +- [ ] **Step 1: Build Android** + +```bash +cd example/android && ANDROID_HOME=~/Library/Android/sdk ./gradlew assembleDebug +``` + +Expected: Codegen generates `NativeBlePlxSpec` Java files, build succeeds. + +- [ ] **Step 2: Fix any compilation errors in android/src/main/kotlin/** +- [ ] **Step 3: Commit fixes if needed** + +### Task 5: iOS build verification + +- [ ] **Step 1: Build iOS** + +```bash +cd example/ios && xcodebuild -workspace BlePlxExample.xcworkspace -scheme BlePlxExample \ + -configuration Debug -sdk iphonesimulator -arch arm64 +``` + +Expected: Codegen generates native headers, Swift compiles, build succeeds. + +- [ ] **Step 2: Fix any compilation errors in ios/** +- [ ] **Step 3: Commit fixes if needed** + +--- + +## Phase 3: Native Unit Tests + +### Task 6: Android unit tests + +**Files:** +- Create: `android/src/test/kotlin/com/bleplx/ScanManagerTest.kt` +- Create: `android/src/test/kotlin/com/bleplx/ErrorConverterTest.kt` +- Create: `android/src/test/kotlin/com/bleplx/PermissionHelperTest.kt` +- Create: `android/src/test/kotlin/com/bleplx/EventSerializerTest.kt` + +- [ ] **Step 1: ScanManagerTest — throttle logic** + +Test: 5 scans in 30s → 6th returns ScanThrottled. Scans spaced by 6s succeed. + +- [ ] **Step 2: ErrorConverterTest — GATT error mapping** + +Test: GATT 133 → ConnectionFailed + retryable. GATT 19 → DeviceDisconnected. GATT 0x3E → ConnectionFailed. SecurityException → ConnectPermissionDenied. + +- [ ] **Step 3: PermissionHelperTest — SDK version gating** + +Test: API 30 → BLUETOOTH + location. API 31+ → BLUETOOTH_SCAN + BLUETOOTH_CONNECT. + +- [ ] **Step 4: Run tests** + +```bash +cd example/android && ANDROID_HOME=~/Library/Android/sdk ./gradlew :react-native-ble-plx:test +``` + +- [ ] **Step 5: Commit** + +### Task 7: iOS unit tests + +**Files:** +- Create: `ios/Tests/GATTOperationQueueTests.swift` +- Create: `ios/Tests/ErrorConverterTests.swift` +- Create: `ios/Tests/EventSerializerTests.swift` + +- [ ] **Step 1: GATTOperationQueueTests** + +Test: operations execute serially, timeout fires, cancel before execution works. + +- [ ] **Step 2: ErrorConverterTests** + +Test: CBError.connectionFailed → ConnectionFailed. CBATTError codes. CBError.peerRemovedPairingInformation → BondLost. + +- [ ] **Step 3: Run tests** + +```bash +cd example/ios && xcodebuild test -workspace BlePlxExample.xcworkspace -scheme BlePlxExample \ + -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16' +``` + +- [ ] **Step 4: Commit** + +--- + +## Phase 4: Hardware Integration + +### Task 8: nRF52840 test firmware + +**Files:** +- Create: `integration-tests/hardware/peripheral-firmware/ble_test_peripheral/ble_test_peripheral.ino` +- Create: `integration-tests/hardware/peripheral-firmware/ble_test_peripheral/config.h` + +Arduino sketch for XIAO nRF52840 that exposes: +- **Test Service** (UUID: `12345678-1234-1234-1234-123456789abc`) + - Read characteristic (returns incrementing counter) + - Write characteristic (echoes back on read) + - Notify characteristic (sends counter every 500ms when subscribed) + - Indicate characteristic (sends counter every 1s when subscribed, requires ACK) +- Configurable MTU response +- Serial command interface: `disconnect`, `set-value `, `toggle-notify`, `reset` +- LED feedback for connection state + +- [ ] **Step 1: Create test firmware** +- [ ] **Step 2: Test with nRF Connect app** +- [ ] **Step 3: Commit** + +### Task 9: Maestro integration test flows + +**Files:** +- Update: `integration-tests/hardware/maestro/scan-pair-sync.yaml` +- Update: `integration-tests/hardware/maestro/disconnect-recovery.yaml` +- Update: `integration-tests/hardware/maestro/indicate-stress.yaml` +- Create: `integration-tests/hardware/maestro/write-read-roundtrip.yaml` + +Pre-requisites documented in each flow: +- BLE permissions pre-granted (`adb shell pm grant` for Android, manual for iOS) +- Test peripheral powered on and advertising + +- [ ] **Step 1: Update flows with real testIDs from example app** +- [ ] **Step 2: Create write-read-roundtrip flow** +- [ ] **Step 3: Commit** diff --git a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md new file mode 100644 index 000000000..c9fdc75e9 --- /dev/null +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -0,0 +1,882 @@ +# react-native-ble-plx v4.0 — TurboModule Rewrite Spec + +**Date:** 2026-03-15 +**Status:** Draft (rev 2 — post Codex review) +**Scope:** Complete native rewrite as TurboModule with modern BLE features, testing, and all audit fixes + +--- + +## 1. Overview + +Rewrite react-native-ble-plx as a React Native TurboModule with two independent native implementations: +- **Android:** Kotlin + Nordic Android-BLE-Library + Scanner Compat Library +- **iOS:** Swift + direct CoreBluetooth with async/await actor-based operation queue + +This addresses all 25 issues from the code audit, adds modern BLE 5.0 features, New Architecture support, and comprehensive automated testing. + +### Goals + +- Zero audit issues remaining +- TurboModule (New Architecture) from day one — no Bridge fallback needed +- Modern BLE features: PHY selection, L2CAP, extended advertising, connection events +- Thread-safe by design on both platforms +- Comprehensive testing: unit (CI), simulated integration (CI), hardware integration (pre-release) +- Backward-compatible JS API where possible (smooth upgrade path from v3.x) + +### Non-Goals + +- Peripheral/server role (central-only, same as v3) +- Web/desktop platform support +- LE Audio / LC3 codec support +- Companion Device Manager (optional future enhancement) + +### Support Floor + +- **React Native 0.82+** (legacy architecture removed in 0.82) +- **iOS 14+** (async/await requires iOS 13, but CB authorization APIs need 14+) +- **Android API 26+** (Android 8.0 — minimum for Nordic BLE Library 2.x) + +### Background BLE + +Background BLE with state restoration is **supported** (not dropped — it's a v3 feature users depend on). The broken state restoration race (audit #11) is fixed, not removed. iOS background modes are opt-in via Expo plugin config, same as v3. + +--- + +## 2. Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ TypeScript API (BleManager, Device, etc.) │ +│ TurboModule Codegen Spec │ +├─────────────────────────────────────────────────┤ +│ JS Event Bridge (backpressure + batching) │ +├──────────────────────┬──────────────────────────┤ +│ Android TurboModule │ iOS TurboModule │ +│ Kotlin │ Swift │ +├──────────────────────┼──────────────────────────┤ +│ Nordic BLE Library │ CoreBluetooth │ +│ + Scanner Compat │ + async/await actor │ +│ + Coroutines │ + operation queue │ +├──────────────────────┼──────────────────────────┤ +│ BluetoothGatt │ CBCentralManager │ +│ BluetoothLeScanner │ CBPeripheral │ +└──────────────────────┴──────────────────────────┘ +``` + +### JS/TS Layer + +- **TurboModule Codegen spec** (`NativeBlePlx.ts`) defines the native interface — methods, typed events, and types. This generates native bindings automatically. **NOT** `RCTEventEmitter` — use the [typed native module events](https://reactnative.dev/docs/0.79/the-new-architecture/native-modules-custom-events) pattern from RN 0.79+. +- **TypeScript types** using `as const` objects (not enums) for runtime values +- **Event bridge** with backpressure and per-stream batching rules (see Event Batching section) +- **Deterministic cancellation**: every operation has a timeout; every Promise resolves or rejects, never hangs +- **Cross-platform error model**: rich error type with platform-specific diagnostic fields (see Error Model section) + +### Android Native (Kotlin) + +- **Nordic Android-BLE-Library v2.11.0** for GATT operations + - Atomic request queues (operation serialization) + - MTU/PHY negotiation (handles Android 14 MTU=517 forced behavior) + - Bonding state management as Kotlin Flow + - Connection lifecycle with retry +- **Nordic Scanner Compat Library** for scanning + - Provides consistent scan API across Android versions + - BLE 5.0 extended advertising scan support + - **Does NOT handle scan throttling automatically** — we must implement our own debouncing (Android 7+ limits to ~5 `startScan` calls per 30s; exceeding this silently returns zero results) +- **Auto-MTU on connect**: Request MTU 517 during connection setup automatically (the #1 user-reported issue with current ble-plx is silent data truncation from the 23-byte default). **Android 14+ caveat:** The system automatically initiates MTU 517 on first connection. Calling `requestMtu` again can cause disconnects on some peripherals. Implementation must check `Build.VERSION.SDK_INT >= 34` and skip explicit MTU request if the system already negotiated ≥ 517. +- **`neverForLocation` flag**: `BLUETOOTH_SCAN` permission must include `android:usesPermissionFlags="neverForLocation"` in manifest (unless the app derives location from BLE scans). Without this, apps still need Location permissions on Android 12+. +- **Kotlin coroutines** for async operations (natural TurboModule Promise mapping) +- **Thread-safe by design**: Nordic library handles GATT threading; coroutine dispatchers for our code +- **Android 12+ permissions**: proper `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT` runtime checks +- **No `enable()`/`disable()`**: removed — use system settings intent instead + +### iOS Native (Swift + ObjC++ adapter) + +- **Direct CoreBluetooth** with custom async/await wrapper +- **Swift actor with custom executor** (Swift 5.9+) backed by the same `DispatchQueue` as CoreBluetooth — solves the `CBPeripheral`/`CBCentralManager` non-Sendable problem +- **`@preconcurrency import CoreBluetooth`** to silence Swift 6 Sendable warnings +- **Thin ObjC++ `.mm` entry point** — Codegen generates ObjC headers, pure-Swift TurboModules are not possible. The chain is: + +``` +JS Spec → Codegen → ObjC++ TurboModule (.mm) → Swift BLEModuleImpl (@objc) → BLEActor → CoreBluetooth +``` + +- **Never pass CB objects across actor boundaries** — extract Sendable values (UUID, Data, String) first +- **GATT operation queue** as a separate actor — CoreBluetooth does NOT serialize operations; rapid successive reads/writes fail silently + - `withResponse` writes await completion callback + - `withoutResponse` writes check `canSendWriteWithoutResponse` + flow control +- **State restoration** via `CBCentralManagerOptionRestoreIdentifierKey` (proper implementation, not the broken `.amb()` race) +- **Modern iOS features**: + - L2CAP channels (`CBPeripheral.openL2CAPChannel`) + - Connection events (`centralManager:connectionEventDidOccurForPeripheral:`, iOS 13+) + - Bluetooth authorization handling (`CBManager.authorization`, iOS 13+) + - iOS 16+ disconnect with reconnection support +- **No retain cycles**: delegate is `weak`, `invalidate` calls `super` +- **Nil-safe**: all methods guard against nil manager, reject Promise with clear error + +--- + +## 3. JS/TS API Changes + +### Breaking changes (v3 → v4) + +| Change | Rationale | +|--------|-----------| +| `enable()` / `disable()` removed | Broken on Android 12+, no-op on iOS. Use system settings. | +| `State` etc. changed from enum to const object | Fix TypeScript type mismatch | +| Constructor no longer silently returns singleton | Explicit `BleManager.getInstance()` instead | +| `monitorCharacteristic` subscription `.remove()` now also cleans up JS listener | Fixes #1308, #1299 | + +### New APIs + +| API | Description | +|-----|-------------| +| `requestPhy(deviceId, txPhy, rxPhy)` | BLE 5.0 PHY selection | +| `readPhy(deviceId)` | Read current PHY | +| `openL2CAPChannel(deviceId, psm)` | iOS L2CAP channel (iOS only) | +| `requestConnectionPriority(deviceId, priority)` | Android: coarse priority (balanced/high/lowPower). iOS: no-op. Fine-grained params are peripheral-firmware-only. | +| `getAuthorizationStatus()` | iOS Bluetooth authorization state | +| `onConnectionEvent(listener)` | iOS 13+ connection events | + +### Event batching + +High-frequency characteristic notifications need backpressure to prevent JS thread overwhelm. + +**Rules:** +- **Scan results**: batched, delivered every 100ms (scans are inherently bursty) +- **Characteristic notifications**: per-stream batching, configurable interval (default 0ms = no batching, immediate delivery) +- **Timing-sensitive notifications** (e.g., indicate with ATT ACK): never batched, always immediate +- **Connection state changes**: never batched, always immediate +- **Max batch size**: 50 events per delivery (prevents single huge payload) +- **Ordering**: monotonic per-stream, cross-stream ordering not guaranteed +- **Bypass**: `monitorCharacteristic(..., { batchInterval: 0 })` disables batching for that stream + +Configurable via `BleManager` constructor option `scanBatchIntervalMs` (default 100) and per-characteristic `batchInterval` option. + +--- + +## 4. Error Model + +Unified error codes across platforms: + +```typescript +export const BleErrorCode = { + // Connection + DeviceNotFound: 0, + DeviceDisconnected: 1, + ConnectionFailed: 2, + ConnectionTimeout: 3, + + // Operations + OperationCancelled: 100, + OperationTimeout: 101, + OperationNotSupported: 102, + OperationInProgress: 103, + + // GATT + CharacteristicNotFound: 200, + ServiceNotFound: 201, + DescriptorNotFound: 202, + CharacteristicWriteFailed: 203, + CharacteristicReadFailed: 204, + MTUNegotiationFailed: 205, + + // Permissions + BluetoothUnauthorized: 300, + BluetoothPoweredOff: 301, + LocationPermissionDenied: 302, + ScanPermissionDenied: 303, + ConnectPermissionDenied: 304, + + // Platform + ManagerNotInitialized: 400, + ManagerDestroyed: 401, + + // Bonding/Pairing + BondingFailed: 500, + BondLost: 501, + PairingRejected: 502, + + // L2CAP + L2CAPChannelFailed: 600, + L2CAPChannelClosed: 601, + + // PHY + PhyNegotiationFailed: 700, + + // Scan + ScanFailed: 800, // nativeCode = Android ScanCallback.SCAN_FAILED_* (1-6) + ScanThrottled: 801, + + UnknownError: 999, +} as const; +``` + +Each error includes: + +```typescript +interface BleError { + code: number; // Unified BleErrorCode + message: string; // Human-readable + isRetryable: boolean; // Hint for caller + + // Context + deviceId?: string; + serviceUUID?: string; + characteristicUUID?: string; + operation?: string; // e.g., 'read', 'write', 'connect', 'scan' + + // Platform diagnostics + platform: 'android' | 'ios'; + nativeDomain?: string; // e.g., 'CBError', 'android.bluetooth' + nativeCode?: number; // Raw platform error code + gattStatus?: number; // Android GATT status (0=success, 133=common failure) + attErrorCode?: number; // ATT protocol error + scanErrorCode?: number; // Android ScanCallback error (1-6) + cbErrorCode?: number; // iOS CBError code +} +``` + +--- + +## 5. Connection State Machine + +``` + ┌──────────────┐ + scan found │ Discovered │ + └──────┬───────┘ + │ connectToDevice() + ▼ + ┌──────────────┐ + │ Connecting │──── timeout/error ────┐ + └──────┬───────┘ │ + │ GATT connected │ + ▼ │ + ┌──────────────┐ │ + │ Connected │──── GATT error ──────┤ + └──────┬───────┘ │ + │ cancelDeviceConnection() │ + ▼ │ + ┌───────────────┐ │ + │ Disconnecting │ │ + └──────┬────────┘ │ + │ │ + ▼ ▼ + ┌──────────────┐ + │ Disconnected │ + └──────────────┘ +``` + +**Retry policy:** Configurable via `connectToDevice(deviceId, { retries: 3, retryDelay: 1000 })`. +- `retries`: max connection attempts (default 1 = no retry) +- `retryDelay`: ms between attempts (default 1000) +- Retryable errors: GATT 133, connection timeout. Non-retryable: permission denied, device not found, user cancelled. +- `isRetryable` field in `BleError` tells the caller whether automatic retry was applicable. + +**State transitions are emitted as events** via the Codegen typed event emitter, not polled. + +--- + +## 5a. Codegen Spec (`NativeBlePlx.ts`) + +The Codegen spec is the contract between JS and native. File MUST be named `NativeBlePlx.ts` (prefix `Native` required by Codegen). All types MUST be defined inline (no imports from other files). Uses `CodegenTypes.EventEmitter` for streaming data (callbacks are single-fire in Codegen). + +**`codegenConfig` in package.json:** +```json +{ + "codegenConfig": { + "name": "NativeBlePlxSpec", + "type": "modules", + "jsSrcsDir": "src/specs", + "android": { "javaPackageName": "com.bleplx" } + } +} +``` + +**Spec file (`src/specs/NativeBlePlx.ts`):** + +```typescript +import type { TurboModule, CodegenTypes } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +// All types must be inline — Codegen ignores imports from other files +// Union types NOT supported — use string with runtime validation + +export type DeviceInfo = Readonly<{ + id: string; // Android: MAC address, iOS: opaque UUID + name: string | null; + rssi: number; + mtu: number; + isConnectable: boolean | null; + serviceUuids: ReadonlyArray; + manufacturerData: string | null; // Base64-encoded +}>; + +export type CharacteristicInfo = Readonly<{ + deviceId: string; + serviceUuid: string; + uuid: string; + value: string | null; // Base64-encoded + isNotifying: boolean; + isIndicatable: boolean; + isReadable: boolean; + isWritableWithResponse: boolean; + isWritableWithoutResponse: boolean; +}>; + +export type ScanResult = Readonly<{ + id: string; + name: string | null; + rssi: number; + serviceUuids: ReadonlyArray; + manufacturerData: string | null; +}>; + +export type ConnectionStateEvent = Readonly<{ + deviceId: string; + state: string; // 'connecting' | 'connected' | 'disconnecting' | 'disconnected' + errorCode: number | null; + errorMessage: string | null; +}>; + +export type CharacteristicValueEvent = Readonly<{ + deviceId: string; + serviceUuid: string; + characteristicUuid: string; + value: string; // Base64-encoded + transactionId: string | null; +}>; + +export type StateChangeEvent = Readonly<{ + state: string; // 'Unknown' | 'Resetting' | 'Unsupported' | 'Unauthorized' | 'PoweredOff' | 'PoweredOn' +}>; + +export type RestoreStateEvent = Readonly<{ + devices: ReadonlyArray; +}>; + +export type BleErrorInfo = Readonly<{ + code: number; + message: string; + isRetryable: boolean; + deviceId: string | null; + serviceUuid: string | null; + characteristicUuid: string | null; + operation: string | null; + platform: string; + nativeDomain: string | null; + nativeCode: number | null; + gattStatus: number | null; + attErrorCode: number | null; +}>; + +export interface Spec extends TurboModule { + // Lifecycle + createClient(restoreStateIdentifier: string | null): Promise; + destroyClient(): Promise; + + // State + state(): Promise; + + // Scanning (scan throttle debouncing built-in) + startDeviceScan(uuids: ReadonlyArray | null, options: Readonly<{ + scanMode?: number; // Android: 0=opportunistic, 1=lowPower, 2=balanced, -1=lowLatency + callbackType?: number; // Android: 1=allMatches, 2=firstMatch, 4=matchLost + legacyScan?: boolean; // false = BLE 5.0 extended advertising + allowDuplicates?: boolean; // iOS only + }> | null): void; + stopDeviceScan(): Promise; + + // Connection (auto-MTU 517 on Android) + connectToDevice(deviceId: string, options: Readonly<{ + autoConnect?: boolean; // Android: true=background, false=direct (default) + timeout?: number; // Connection timeout in ms + retries?: number; // Number of retry attempts (default 1) + retryDelay?: number; // Ms between retries (default 1000) + requestMtu?: number; // Auto-request MTU after connect (default 517 on Android, ignored on iOS) + }> | null): Promise; + cancelDeviceConnection(deviceId: string): Promise; + isDeviceConnected(deviceId: string): Promise; + + // Discovery + discoverAllServicesAndCharacteristics(deviceId: string, transactionId: string | null): Promise; + + // Read/Write + readCharacteristic(deviceId: string, serviceUuid: string, characteristicUuid: string, transactionId: string | null): Promise; + writeCharacteristic(deviceId: string, serviceUuid: string, characteristicUuid: string, value: string, withResponse: boolean, transactionId: string | null): Promise; + + // Monitor (use EventEmitter, not callback — callbacks are single-fire) + // subscriptionType: 'indicate' | 'notify' | null (null = auto-detect from characteristic + // properties, preferring notifications when both are available) + monitorCharacteristic(deviceId: string, serviceUuid: string, characteristicUuid: string, subscriptionType: string | null, transactionId: string | null): void; + + // MTU + getMtu(deviceId: string): Promise; + requestMtu(deviceId: string, mtu: number, transactionId: string | null): Promise; + + // PHY (Android only — iOS returns current PHY info but cannot set) + requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise; + readPhy(deviceId: string): Promise; + + // Connection Priority (Android only — coarse: 0=balanced, 1=high, 2=lowPower) + // NOTE: Fine-grained interval/latency/timeout is NOT settable from app-level APIs. + // Only the peripheral firmware can request specific connection parameters. + requestConnectionPriority(deviceId: string, priority: number): Promise; + + // L2CAP (iOS only — PSM must be dynamic, read from GATT characteristic) + openL2CAPChannel(deviceId: string, psm: number): Promise>; + writeL2CAPChannel(channelId: number, data: string): Promise; + closeL2CAPChannel(channelId: number): Promise; + + // Bonding (Android: system bonded devices. iOS: not available — returns empty array) + getBondedDevices(): Promise>; + + // Authorization (iOS only — Android returns 'Authorized' always) + getAuthorizationStatus(): Promise; + + // Cancellation + cancelTransaction(transactionId: string): Promise; + + // ─── Typed Event Emitters (CodegenTypes.EventEmitter) ─── + readonly onScanResult: CodegenTypes.EventEmitter; + readonly onConnectionStateChange: CodegenTypes.EventEmitter; + readonly onCharacteristicValueUpdate: CodegenTypes.EventEmitter; + readonly onStateChange: CodegenTypes.EventEmitter; + readonly onRestoreState: CodegenTypes.EventEmitter; + readonly onError: CodegenTypes.EventEmitter; + readonly onBondStateChange: CodegenTypes.EventEmitter>; // bondState: 'none' | 'bonding' | 'bonded' + readonly onConnectionEvent: CodegenTypes.EventEmitter>; // iOS 13+ connection events + readonly onL2CAPData: CodegenTypes.EventEmitter>; // L2CAP incoming data + readonly onL2CAPClose: CodegenTypes.EventEmitter>; +} + +export default TurboModuleRegistry.get('NativeBlePlx'); +``` + +**Key design decisions from research:** +- `TurboModuleRegistry.get` (nullable) for graceful degradation, not `getEnforcing` +- All types inline — Codegen ignores imports +- No union types — use string with runtime validation for enum-like values +- Events as `readonly` `CodegenTypes.EventEmitter` properties — not callbacks (single-fire only) +- `Object` replaced with typed `Readonly<{...}>` throughout — `Object` is an escape hatch +- `requestConnectionParameters` replaced with `requestConnectionPriority` (coarse, Android-only) — fine-grained params are peripheral-firmware-only +- L2CAP expanded to stream lifecycle: open/write/close (not just open) +- `optionsJson` as string for complex options (avoids Codegen type limitations) + +**Device identifier note:** Android uses MAC addresses (`AA:BB:CC:DD:EE:FF`), iOS uses opaque per-phone UUIDs (not per-app). The UUID can change after Bluetooth settings reset. There is NO cross-platform stable identifier — embed unique IDs in GATT characteristics or manufacturer advertising data if needed. + +--- + +## 5b. Background BLE — Platform-Specific Requirements + +**iOS:** +- Requires `UIBackgroundModes` with `bluetooth-central` in Info.plist +- Background scanning MUST specify service UUID filters — passing `nil` returns zero results in background +- `CBCentralManagerScanOptionAllowDuplicatesKey` is ignored in background +- Scan intervals increase significantly in background +- State restoration requires `CBCentralManagerOptionRestoreIdentifierKey` +- Only GATT characteristic notifications wake suspended apps — L2CAP does NOT + +**Android:** +- Background BLE connections require a foreground service with `android:foregroundServiceType="connectedDevice"` (Android 12+ restricts starting foreground services from background) +- Background scanning options: foreground service (most reliable), `PendingIntent`-based scanning (system-managed), or `CompanionDeviceManager` (Android 12+, limited) +- Android 14+ foreground service type declaration required in manifest + +**Both platforms:** Background BLE is opt-in via Expo config plugin. The library documents requirements but does not automatically configure background mode. + +## 5c. Bonding and Encryption Contract + +On Android, `getBondState()` only checks whether bond info exists — it does NOT verify the link is actually encrypted. An attacker can spoof MAC addresses and connect unencrypted. Use Nordic's `ensureBond()` after connection to verify actual link encryption, or read a protected characteristic to trigger automatic pairing. + +On iOS, the system handles bonding transparently when the peripheral requires encryption. `setNotifyValue` on an encrypted characteristic triggers pairing automatically. There is no explicit `ensureBond()` equivalent. + +`getBondedDevices()` is Android-only. iOS has no equivalent of `BluetoothAdapter.getBondedDevices()` — returns empty array on iOS. + +--- + +## 6. Android Implementation Details + +### Dependencies + +```kotlin +dependencies { + implementation("no.nordicsemi.android:ble:2.11.0") + implementation("no.nordicsemi.android:ble-ktx:2.11.0") // Kotlin extensions + implementation("no.nordicsemi.android.support.v18:scanner:1.6.0") // Scanner Compat + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") +} +``` + +### Key architecture + +```kotlin +// TurboModule entry point +class BlePlxModule(reactContext: ReactApplicationContext) : BlePlxSpec(reactContext) { + private val scanner = BluetoothLeScannerCompat.getScanner() + private val connections = ConcurrentHashMap() + private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) + + override fun startDeviceScan(uuids: ReadableArray?, options: ReadableMap?, promise: Promise) { + scope.launch { + // Nordic Scanner Compat handles throttling + } + } + + override fun connectToDevice(deviceId: String, options: ReadableMap?, promise: Promise) { + scope.launch { + val manager = BleManagerWrapper(reactApplicationContext) + connections[deviceId] = manager + manager.connect(deviceId, options) + promise.resolve(manager.toJS()) + } + } +} + +// Per-device GATT manager using Nordic BLE Library +class BleManagerWrapper(context: Context) : BleManager(context) { + // Nordic handles operation queue, MTU, PHY, bonding + // All callbacks are coroutine-safe via ktx extensions +} +``` + +### Permission handling + +```kotlin +// Runtime permission checks before every BLE operation +private fun requireScanPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) + != PackageManager.PERMISSION_GRANTED) { + throw BleError(BleErrorCode.ScanPermissionDenied) + } + } +} +``` + +### Android 14 MTU handling + +Nordic BLE Library handles the forced MTU=517 behavior. We document it and expose the actual negotiated MTU to JS. + +--- + +## 6. iOS Implementation Details + +### Architecture + +```swift +// ObjC++ entry point (.mm file) — generated by Codegen, delegates to Swift +// BlePlx.mm conforms to NativeBlePlxSpec and calls into BLEModuleImpl + +// Swift implementation — exposed via @objc +@objc public class BLEModuleImpl: NSObject { + private var bleActor: BLEActor? + + @objc public func createClient(_ restoreId: String?) async throws { + bleActor?.invalidate() + bleActor = BLEActor(restoreId: restoreId) + } +} + +// BLE actor with custom executor on CoreBluetooth's queue +@preconcurrency import CoreBluetooth + +actor BLEActor { + let queue = DispatchQueue(label: "com.bleplx.ble") + + // Custom executor: actor runs on same queue as CoreBluetooth + nonisolated var unownedExecutor: UnownedSerialExecutor { + queue.asUnownedSerialExecutor() + } + + private lazy var centralManager = CBCentralManager( + delegate: delegateHandler, queue: queue + ) + private let delegateHandler = CentralManagerDelegate() + private var peripherals: [UUID: PeripheralWrapper] = [:] + + func scan(serviceUUIDs: [CBUUID]?, options: [String: Any]?) -> AsyncStream { ... } + func connect(peripheralId: UUID, options: ConnectionOptions) async throws -> PeripheralWrapper { ... } + func disconnect(peripheralId: UUID) async throws { ... } +} + +// Per-peripheral GATT operation queue (also an actor on the BLE queue) +actor PeripheralWrapper { + private let peripheral: CBPeripheral // Safe: same queue as actor executor + private let delegateHandler: PeripheralDelegate + private let operationQueue = GATTOperationQueue() + + func discoverServices(_ uuids: [CBUUID]?) async throws -> [CBService] { ... } + + func readCharacteristic(_ characteristic: CBCharacteristic) async throws -> Data { + // Queued: only one outstanding GATT operation at a time + return try await operationQueue.enqueue { + self.peripheral.readValue(for: characteristic) + } + } + + func writeCharacteristic(_ characteristic: CBCharacteristic, data: Data, type: CBCharacteristicWriteType) async throws { + if type == .withoutResponse { + // Flow control: check canSendWriteWithoutResponse first + while !peripheral.canSendWriteWithoutResponse { + try await delegateHandler.waitForWriteReady() + } + } + try await operationQueue.enqueue { + self.peripheral.writeValue(data, for: characteristic, type: type) + } + } + + // L2CAP — dynamic PSM only (no static PSMs on iOS) + // Must retain channel + peripheral strongly; schedule on correct RunLoop + // L2CAP does NOT wake suspended apps — only GATT notifications do + func openL2CAPChannel(psm: CBL2CAPPSM) async throws -> L2CAPChannelWrapper { ... } +} +``` + +### Delegate-to-async bridging + +```swift +// Using CheckedContinuation for one-shot operations +func readCharacteristic(_ characteristic: CBCharacteristic) async throws -> Data { + return try await withCheckedThrowingContinuation { continuation in + pendingReads[characteristic.uuid] = continuation + peripheral.readValue(for: characteristic) + } +} + +// Using AsyncStream for ongoing notifications +func monitorCharacteristic(_ characteristic: CBCharacteristic) -> AsyncStream { + AsyncStream { continuation in + notificationStreams[characteristic.uuid] = continuation + peripheral.setNotifyValue(true, for: characteristic) + continuation.onTermination = { _ in + self.peripheral.setNotifyValue(false, for: characteristic) + } + } +} +``` + +### State restoration + +```swift +// BOOTSTRAP TIMING: On background relaunch triggered by Bluetooth state restoration, +// the native .mm module (or Expo plugin AppDelegate extension) must eagerly instantiate +// CBCentralManager with the restore identifier during application(_:willFinishLaunchingWithOptions:) +// — BEFORE the JS bridge is ready. Buffer the willRestoreState payload until JS calls +// createClient() and subscribes to onRestoreState. +// +// IMPORTANT: willRestoreState fires BEFORE centralManagerDidUpdateState. +// Do NOT assume Bluetooth state is ready here. Only re-attach delegates and store peripheral refs. +// State restoration only works for system-terminated apps — force-quit by user disables it. +func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) { + if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] { + for peripheral in peripherals { + let wrapper = PeripheralWrapper(peripheral: peripheral) + self.peripherals[peripheral.identifier] = wrapper + } + } + // Correctly read scan options (fix copy-paste bug from v3) + if let scanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any] { + // Handle restored scan state + } +} +``` + +--- + +## 7. Testing Strategy + +### Unit tests (CI — runs on every PR) + +| Layer | Framework | Coverage | +|-------|-----------|----------| +| JS/TS | Jest | Protocol parsing, state management, event batching, error mapping | +| Android | JUnit + MockK | Permission checks, error conversion, event serialization, Nordic integration mocks | +| iOS | XCTest | Actor operation queue, delegate-to-async bridging, error conversion, state restoration | + +### Simulated integration tests (CI) + +Mock GATT peripherals that exercise the full protocol stack without real Bluetooth hardware: + +**Android:** Create a custom `MockBleManager` extending Nordic's `BleManager` with a mock GATT layer (Nordic Android-BLE-Library 2.x does not include mock utilities — those are in the newer Kotlin-BLE-Library which isn't production-ready). Alternatively, use Robolectric shadows for `BluetoothGatt`. Test: +- Full connect → discover → read → write → notify → disconnect cycle +- Indicate-based streaming (Smart Cap Rx protocol) +- Watermark ACK flow +- MTU negotiation +- Connection parameter changes +- Error scenarios (disconnect mid-transfer, timeout, permission denied) +- Permission revoke mid-scan +- Bluetooth off mid-operation +- Bond failure during pairing +- State restoration after app kill (iOS) +- Event batch ordering under high notification load + +**iOS:** Use `CBMCentralManagerMock` from Nordic's [CoreBluetoothMock](https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock) library. Same test scenarios as Android. + +**JS:** Mock the TurboModule native interface and test the full JS flow: +- Scan → connect → sync → disconnect lifecycle +- Event batching under high notification frequency +- Cancellation and timeout behavior +- Error propagation + +### Hardware integration tests (pre-release) + +Dual-device test framework: +- **Peripheral device:** nRF52840 DK (or XIAO) running a test firmware that exposes known GATT services +- **Central device:** Phone running the test app +- **Test runner:** Maestro or Detox controlling the phone, serial commands to the peripheral +- **Test cases:** Real BLE pairing, MTU negotiation, PHY selection, indicate streaming, disconnect recovery + +Smart Cap Rx protocol as the primary integration test: +- Time Sync write +- Event stream indicate transfer +- Watermark ACK +- Factory reset double-write + +--- + +## 8. Smart Cap Rx Specific Features + +Features our app needs that must work correctly: + +| Feature | Status in v3 | v4 | +|---------|-------------|-----| +| Indicate (not Notify) | Untested but code path exists | First-class support, integration tested | +| Write With Response | Works but no ATT confirmation exposed | Promise resolves on ATT ACK | +| Bonded LESC Just Works | Untested | Tested with nRF52840 | +| Connection parameters | Android: 1ms delay (broken), iOS: no-op | Both platforms: proper negotiation | +| MTU negotiation | Works but iOS hardcodes 23 for scanned | Correct on both platforms | +| Event batching | Not handled — notification storms crash JS | Configurable batching | +| Reliable disconnect detection | Android: null error always | Proper GATT error propagation | + +--- + +## 9. Migration Guide (v3 → v4) + +### Package changes + +```diff +- "react-native-ble-plx": "^3.5.1" ++ "react-native-ble-plx": "^4.0.0" +``` + +Requires React Native 0.82+ (legacy architecture removed in 0.82). + +### API changes + +```typescript +// v3: enum (incorrect TypeScript) +import { State } from 'react-native-ble-plx'; +if (state === State.PoweredOn) { ... } + +// v4: const object (correct) +import { State } from 'react-native-ble-plx'; +if (state === State.PoweredOn) { ... } // Same usage, correct types +``` + +```typescript +// v3: enable() — broken on Android 12+ +await manager.enable(); + +// v4: removed — use system settings +import { Linking } from 'react-native'; +Linking.openSettings(); +``` + +```typescript +// v3: monitor subscription cleanup broken +const sub = manager.monitorCharacteristicForDevice(...); +sub.remove(); // BUG: doesn't clean up JS listener + +// v4: fixed +const sub = manager.monitorCharacteristicForDevice(...); +sub.remove(); // Properly cleans up native + JS listeners +``` + +### New features + +```typescript +// PHY selection (Android only) +await manager.requestPhy(deviceId, PhyType.LE_2M, PhyType.LE_2M); + +// Connection priority (Android only — coarse levels, not fine-grained params) +// Fine-grained interval/latency/timeout can only be set by peripheral firmware +await manager.requestConnectionPriority(deviceId, ConnectionPriority.HIGH); + +// L2CAP (iOS only) +const channel = await manager.openL2CAPChannel(deviceId, 0x0080); +``` + +--- + +## 10. File Structure + +``` +react-native-ble-plx/ +├── src/ ← TypeScript API + TurboModule spec +│ ├── NativeBlePlx.ts ← Codegen spec +│ ├── BleManager.ts ← Main API class +│ ├── Device.ts +│ ├── Characteristic.ts +│ ├── Service.ts +│ ├── Descriptor.ts +│ ├── BleError.ts ← Unified error model +│ ├── types.ts ← All TypeScript types (const objects) +│ └── EventBatcher.ts ← JS-side notification batching +├── android/ +│ └── src/main/kotlin/com/bleplx/ +│ ├── BlePlxModule.kt ← TurboModule entry point +│ ├── BleManagerWrapper.kt ← Per-device Nordic BLE Manager +│ ├── ScanManager.kt ← Nordic Scanner Compat wrapper +│ ├── PermissionHelper.kt ← Runtime permission checks +│ ├── EventSerializer.kt ← Native → JS event conversion +│ └── ErrorConverter.kt ← GATT error → unified error code +├── ios/ +│ ├── BlePlx.mm ← ObjC++ TurboModule entry (Codegen-generated base class) +│ ├── BLEModuleImpl.swift ← Swift @objc implementation (delegates to actor) +│ ├── BleActor.swift ← Central manager actor +│ ├── PeripheralWrapper.swift ← Per-peripheral operation queue actor +│ ├── ScanManager.swift ← Scan with AsyncStream +│ ├── EventSerializer.swift ← Native → JS event conversion +│ ├── ErrorConverter.swift ← CB error → unified error code +│ └── StateRestoration.swift ← Background restoration handler +├── __tests__/ ← JS unit tests +├── android/src/test/ ← Android unit tests +├── ios/Tests/ ← iOS unit tests +├── integration-tests/ +│ ├── simulated/ ← Mock GATT peripheral tests (CI) +│ └── hardware/ ← Dual-device test framework (pre-release) +└── plugin/ ← Expo config plugin (updated) +``` + +--- + +## 11. Resolved Audit Issues + +All 25 issues from AUDIT.md are resolved by the rewrite: + +| # | Issue | Resolution | +|---|-------|-----------| +| 1-2 | Thread-unsafe shared state (iOS+Android) | Swift actors (iOS), ConcurrentHashMap + coroutines (Android) | +| 3 | Monitor subscription cleanup (JS) | Subscription `.remove()` cleans up both native + JS | +| 4 | `enable()` broken Android 12+ | Removed. Use system settings. | +| 5 | Empty AndroidManifestNew.xml | Proper manifest for namespace mode | +| 6 | Scan listener leak (JS) | Stop previous scan before starting new | +| 7 | Disconnection monitor leak (iOS) | Actor tracks all subscriptions | +| 8 | `getState()` permission crash (Android) | Runtime permission check | +| 9 | Hardcoded MTU 23 (iOS) | Report actual `maximumWriteValueLength` | +| 10 | `requestConnectionPriority` no-op (iOS) | Documented as Android-only, iOS returns error | +| 11 | State restoration race (iOS) | Proper async handling, no `.amb()` | +| 12 | Never-settled promises (JS) | Every operation has timeout, always resolves/rejects | +| 13 | Silent reconnect on connect (Android) | Configurable behavior, not silent | +| 14 | Wrong TypeScript types | Const objects with `as const`, all types correct | +| 15 | `disable()` crash Android 13+ | Removed | +| 16 | JSON error no escaping (iOS) | Proper Codable serialization | +| 17 | Static IdGenerator (Android) | Per-instance IDs | +| 18 | Global RxJavaPlugins (Android) | No RxJava | +| 19 | Promise double-resolution (iOS) | Actor isolation prevents races | +| 20 | `RestoredState` wrong key (iOS) | Fixed: `CBCentralManagerRestoredStateScanOptionsKey` | +| 21 | Disconnect null error (Android) | Propagate GATT error code | +| 22 | Strong delegate retain cycle (iOS) | Weak delegate | +| 23 | `createClient` re-entry (iOS) | Invalidate previous before creating new | +| 24 | Nil manager hangs promises (iOS) | Guard + reject with `ManagerNotInitialized` | +| 25 | `invalidate` no super (iOS) | Calls `super.invalidate()` | diff --git a/example-expo/.gitignore b/example-expo/.gitignore index 05647d55c..f8c6c2e83 100644 --- a/example-expo/.gitignore +++ b/example-expo/.gitignore @@ -7,8 +7,10 @@ node_modules/ .expo/ dist/ web-build/ +expo-env.d.ts # Native +.kotlin/ *.orig.* *.jks *.p8 @@ -33,3 +35,9 @@ yarn-error.* # typescript *.tsbuildinfo + +app-example + +# generated native folders +/ios +/android diff --git a/example-expo/App.tsx b/example-expo/App.tsx deleted file mode 100644 index f0866fa2a..000000000 --- a/example-expo/App.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import { AppComponent } from './src/AppComponent' - -export function App() { - return -} diff --git a/example-expo/README.md b/example-expo/README.md new file mode 100644 index 000000000..8d8053f82 --- /dev/null +++ b/example-expo/README.md @@ -0,0 +1,35 @@ +# BLE PLX Expo Example + +Expo SDK 55 example app for `react-native-ble-plx` v4. Uses expo-router for file-based navigation and expo-dev-client for native BLE access. + +## Prerequisites + +- Node >= 20 +- Physical device (BLE does not work in simulators/emulators) +- Xcode 26+ (iOS) or Android Studio (Android) + +## Setup + +```bash +yarn install +``` + +## Running + +### iOS + +```bash +npx expo run:ios --device +``` + +### Android + +```bash +npx expo run:android --device +``` + +## Notes + +- This app does **not** work with Expo Go. It requires a development build (`expo-dev-client`) because BLE needs native modules. +- BLE permissions are configured automatically by the `react-native-ble-plx` config plugin in `app.config.ts`. +- The app links to the parent `react-native-ble-plx` package via `file:../` for local development. diff --git a/example-expo/android/.gitignore b/example-expo/android/.gitignore deleted file mode 100644 index 8a6be0771..000000000 --- a/example-expo/android/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# OSX -# -.DS_Store - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml -*.hprof -.cxx/ - -# Bundle artifacts -*.jsbundle diff --git a/example-expo/android/app/build.gradle b/example-expo/android/app/build.gradle deleted file mode 100644 index 5aa3e210f..000000000 --- a/example-expo/android/app/build.gradle +++ /dev/null @@ -1,172 +0,0 @@ -apply plugin: "com.android.application" -apply plugin: "org.jetbrains.kotlin.android" -apply plugin: "com.facebook.react" - -def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() - -/** - * This is the configuration block to customize your React Native Android app. - * By default you don't need to apply any configuration, just uncomment the lines you need. - */ -react { - entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) - reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() - hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" - codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() - - // Use Expo CLI to bundle the app, this ensures the Metro config - // works correctly with Expo projects. - cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) - bundleCommand = "export:embed" - - /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '..' - // root = file("../") - // The folder where the react-native NPM package is. Default is ../node_modules/react-native - // reactNativeDir = file("../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen - // codegenDir = file("../node_modules/@react-native/codegen") - - /* Variants */ - // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. - // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] - - /* Bundling */ - // A list containing the node command and its flags. Default is just 'node'. - // nodeExecutableAndArgs = ["node"] - - // - // The path to the CLI configuration file. Default is empty. - // bundleConfig = file(../rn-cli.config.js) - // - // The name of the generated asset file containing your JS bundle - // bundleAssetName = "MyApplication.android.bundle" - // - // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' - // entryFile = file("../js/MyApplication.android.js") - // - // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle - // extraPackagerArgs = [] - - /* Hermes Commands */ - // The hermes compiler command to run. By default it is 'hermesc' - // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" - // - // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" - // hermesFlags = ["-O", "-output-source-map"] -} - -/** - * Set this to true to Run Proguard on Release builds to minify the Java bytecode. - */ -def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() - -/** - * The preferred build flavor of JavaScriptCore (JSC) - * - * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` - * - * The international variant includes ICU i18n library and necessary data - * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that - * this variant is about 6MiB larger per architecture than default. - */ -def jscFlavor = 'org.webkit:android-jsc:+' - -android { - ndkVersion rootProject.ext.ndkVersion - - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdk rootProject.ext.compileSdkVersion - - namespace 'com.withintent.bleplxexample' - defaultConfig { - applicationId 'com.withintent.bleplxexample' - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0.0" - } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' - } - } - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. - signingConfig signingConfigs.debug - shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - } - } - packagingOptions { - jniLibs { - useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) - } - } -} - -// Apply static values from `gradle.properties` to the `android.packagingOptions` -// Accepts values in comma delimited lists, example: -// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini -["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> - // Split option: 'foo,bar' -> ['foo', 'bar'] - def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); - // Trim all elements in place. - for (i in 0.. 0) { - println "android.packagingOptions.$prop += $options ($options.length)" - // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' - options.each { - android.packagingOptions[prop] += it - } - } -} - -dependencies { - // The version of react-native is set by the React Native Gradle Plugin - implementation("com.facebook.react:react-android") - - def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; - def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; - def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; - - if (isGifEnabled) { - // For animated gif support - implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") - } - - if (isWebpEnabled) { - // For webp support - implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") - if (isWebpAnimatedEnabled) { - // Animated webp support - implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") - } - } - - if (hermesEnabled.toBoolean()) { - implementation("com.facebook.react:hermes-android") - } else { - implementation jscFlavor - } -} - -apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); -applyNativeModulesAppBuildGradle(project) diff --git a/example-expo/android/app/proguard-rules.pro b/example-expo/android/app/proguard-rules.pro deleted file mode 100644 index 551eb41da..000000000 --- a/example-expo/android/app/proguard-rules.pro +++ /dev/null @@ -1,14 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# react-native-reanimated --keep class com.swmansion.reanimated.** { *; } --keep class com.facebook.react.turbomodule.** { *; } - -# Add any project specific keep options here: diff --git a/example-expo/android/app/src/debug/AndroidManifest.xml b/example-expo/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 3ec2507ba..000000000 --- a/example-expo/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/example-expo/android/app/src/main/AndroidManifest.xml b/example-expo/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index b1ef69223..000000000 --- a/example-expo/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainActivity.kt b/example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainActivity.kt deleted file mode 100644 index a5f087dd1..000000000 --- a/example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainActivity.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.withintent.bleplxexample - -import android.os.Build -import android.os.Bundle - -import com.facebook.react.ReactActivity -import com.facebook.react.ReactActivityDelegate -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled -import com.facebook.react.defaults.DefaultReactActivityDelegate - -import expo.modules.ReactActivityDelegateWrapper - -class MainActivity : ReactActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - // Set the theme to AppTheme BEFORE onCreate to support - // coloring the background, status bar, and navigation bar. - // This is required for expo-splash-screen. - setTheme(R.style.AppTheme); - super.onCreate(null) - } - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - override fun getMainComponentName(): String = "main" - - /** - * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] - * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] - */ - override fun createReactActivityDelegate(): ReactActivityDelegate { - return ReactActivityDelegateWrapper( - this, - BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, - object : DefaultReactActivityDelegate( - this, - mainComponentName, - fabricEnabled - ){}) - } - - /** - * Align the back button behavior with Android S - * where moving root activities to background instead of finishing activities. - * @see onBackPressed - */ - override fun invokeDefaultOnBackPressed() { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { - if (!moveTaskToBack(false)) { - // For non-root activities, use the default implementation to finish them. - super.invokeDefaultOnBackPressed() - } - return - } - - // Use the default back button implementation on Android S - // because it's doing more than [Activity.moveTaskToBack] in fact. - super.invokeDefaultOnBackPressed() - } -} diff --git a/example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainApplication.kt b/example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainApplication.kt deleted file mode 100644 index cf295b0b7..000000000 --- a/example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainApplication.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.withintent.bleplxexample - -import android.app.Application -import android.content.res.Configuration - -import com.facebook.react.PackageList -import com.facebook.react.ReactApplication -import com.facebook.react.ReactNativeHost -import com.facebook.react.ReactPackage -import com.facebook.react.ReactHost -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load -import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.soloader.SoLoader - -import expo.modules.ApplicationLifecycleDispatcher -import expo.modules.ReactNativeHostWrapper - -class MainApplication : Application(), ReactApplication { - - override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( - this, - object : DefaultReactNativeHost(this) { - override fun getPackages(): List { - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - return PackageList(this).packages - } - - override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" - - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED - } - ) - - override val reactHost: ReactHost - get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) - - override fun onCreate() { - super.onCreate() - SoLoader.init(this, false) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } - ApplicationLifecycleDispatcher.onApplicationCreate(this) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) - } -} diff --git a/example-expo/android/app/src/main/res/drawable-hdpi/splashscreen_image.png b/example-expo/android/app/src/main/res/drawable-hdpi/splashscreen_image.png deleted file mode 100644 index c52c2c680..000000000 Binary files a/example-expo/android/app/src/main/res/drawable-hdpi/splashscreen_image.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/drawable-mdpi/splashscreen_image.png b/example-expo/android/app/src/main/res/drawable-mdpi/splashscreen_image.png deleted file mode 100644 index c52c2c680..000000000 Binary files a/example-expo/android/app/src/main/res/drawable-mdpi/splashscreen_image.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png b/example-expo/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png deleted file mode 100644 index c52c2c680..000000000 Binary files a/example-expo/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png b/example-expo/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png deleted file mode 100644 index c52c2c680..000000000 Binary files a/example-expo/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png b/example-expo/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png deleted file mode 100644 index c52c2c680..000000000 Binary files a/example-expo/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/drawable/rn_edit_text_material.xml b/example-expo/android/app/src/main/res/drawable/rn_edit_text_material.xml deleted file mode 100644 index 5c25e728e..000000000 --- a/example-expo/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - diff --git a/example-expo/android/app/src/main/res/drawable/splashscreen.xml b/example-expo/android/app/src/main/res/drawable/splashscreen.xml deleted file mode 100644 index c8568e162..000000000 --- a/example-expo/android/app/src/main/res/drawable/splashscreen.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 3941bea9b..000000000 --- a/example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 3941bea9b..000000000 --- a/example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0a61c1b27..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index ac03dbf69..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 26deb9725..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fd1a2c712..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index e1173a94d..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 4d3ac7a4e..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d57caa303..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index ff086fdc3..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 95ac5824e..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0a07e73ac..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index f7f1d0690..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 6508f7420..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 499e372bd..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 49a464ee3..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index d54716bfa..000000000 Binary files a/example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/example-expo/android/app/src/main/res/values-night/colors.xml b/example-expo/android/app/src/main/res/values-night/colors.xml deleted file mode 100644 index 3c05de5be..000000000 --- a/example-expo/android/app/src/main/res/values-night/colors.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/example-expo/android/app/src/main/res/values/colors.xml b/example-expo/android/app/src/main/res/values/colors.xml deleted file mode 100644 index f387b9011..000000000 --- a/example-expo/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - #ffffff - #ffffff - #023c69 - #ffffff - \ No newline at end of file diff --git a/example-expo/android/app/src/main/res/values/strings.xml b/example-expo/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 8d4b90da5..000000000 --- a/example-expo/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - example-expo - contain - false - \ No newline at end of file diff --git a/example-expo/android/app/src/main/res/values/styles.xml b/example-expo/android/app/src/main/res/values/styles.xml deleted file mode 100644 index f03e23f85..000000000 --- a/example-expo/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example-expo/android/build.gradle b/example-expo/android/build.gradle deleted file mode 100644 index 932bf7b34..000000000 --- a/example-expo/android/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - ext { - buildToolsVersion = findProperty('android.buildToolsVersion') ?: '34.0.0' - minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '23') - compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '34') - targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34') - kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.23' - - ndkVersion = "26.1.10909125" - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath('com.android.tools.build:gradle') - classpath('com.facebook.react:react-native-gradle-plugin') - classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') - } -} - -apply plugin: "com.facebook.react.rootproject" - -allprojects { - repositories { - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) - } - maven { - // Android JSC is installed from npm - url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')) - } - - google() - mavenCentral() - maven { url 'https://www.jitpack.io' } - } -} diff --git a/example-expo/android/gradle.properties b/example-expo/android/gradle.properties deleted file mode 100644 index 41b173c48..000000000 --- a/example-expo/android/gradle.properties +++ /dev/null @@ -1,56 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true - -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true - -# Use this property to specify which architecture you want to build. -# You can also override it from the CLI using -# ./gradlew -PreactNativeArchitectures=x86_64 -reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 - -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# your application. You should enable this flag either if you want -# to write custom TurboModules/Fabric components OR use libraries that -# are providing them. -newArchEnabled=false - -# Use this property to enable or disable the Hermes JS engine. -# If set to false, you will be using JSC instead. -hermesEnabled=true - -# Enable GIF support in React Native images (~200 B increase) -expo.gif.enabled=true -# Enable webp support in React Native images (~85 KB increase) -expo.webp.enabled=true -# Enable animated webp support (~3.4 MB increase) -# Disabled by default because iOS doesn't support animated webp -expo.webp.animated=false - -# Enable network inspector -EX_DEV_CLIENT_NETWORK_INSPECTOR=true - -# Use legacy packaging to compress native libraries in the resulting APK. -expo.useLegacyPackaging=false diff --git a/example-expo/android/gradle/wrapper/gradle-wrapper.jar b/example-expo/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917..000000000 Binary files a/example-expo/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/example-expo/android/settings.gradle b/example-expo/android/settings.gradle deleted file mode 100644 index 316ece7ed..000000000 --- a/example-expo/android/settings.gradle +++ /dev/null @@ -1,18 +0,0 @@ -rootProject.name = 'example-expo' - -dependencyResolutionManagement { - versionCatalogs { - reactAndroidLibs { - from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml"))) - } - } -} - -apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); -useExpoModules() - -apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); -applyNativeModulesSettingsGradle(settings) - -include ':app' -includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile()) diff --git a/example-expo/app.json b/example-expo/app.json index a94ff0013..e41acec3d 100644 --- a/example-expo/app.json +++ b/example-expo/app.json @@ -1,29 +1,44 @@ { "expo": { - "name": "example-expo", - "slug": "example-expo", + "name": "BLE PLX Example", + "slug": "ble-plx-expo-example", "version": "1.0.0", "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, + "icon": "./assets/images/icon.png", + "scheme": "bleplxexample", + "userInterfaceStyle": "automatic", "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.withintent.bleplxexample" + "bundleIdentifier": "com.iotashan.example.bleplxexpo" }, "android": { + "package": "com.bleplx.expoexample", "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "com.withintent.bleplxexample" + "backgroundColor": "#3737DC", + "foregroundImage": "./assets/images/icon.png" + } }, - "web": { - "favicon": "./assets/favicon.png" + "plugins": [ + "expo-router", + [ + "expo-build-properties", + { + "ios": { + "deploymentTarget": "17.0" + } + } + ], + [ + "react-native-ble-plx", + { + "isBackgroundEnabled": true, + "modes": ["central"], + "bluetoothAlwaysPermission": "This app uses Bluetooth to communicate with BLE devices.", + "neverForLocation": false + } + ] + ], + "experiments": { + "typedRoutes": true } } } diff --git a/example-expo/app/_layout.tsx b/example-expo/app/_layout.tsx new file mode 100644 index 000000000..f0351a044 --- /dev/null +++ b/example-expo/app/_layout.tsx @@ -0,0 +1,37 @@ +import { useEffect, useRef } from 'react'; +import { Stack } from 'expo-router'; +import { BleManager } from 'react-native-ble-plx'; + +// Singleton BleManager — created once at module level +const bleManager = new BleManager(); + +/** Access the shared BleManager instance from any screen. */ +export function useBleManager(): BleManager { + return bleManager; +} + +export default function RootLayout() { + const initialized = useRef(false); + + useEffect(() => { + if (!initialized.current) { + initialized.current = true; + bleManager.createClient().catch((e: any) => { + console.warn('Failed to create BLE client:', e); + }); + } + + return () => { + bleManager.destroyClient().catch(() => {}); + }; + }, []); + + return ( + + + + + + + ); +} diff --git a/example-expo/app/characteristic.tsx b/example-expo/app/characteristic.tsx new file mode 100644 index 000000000..6c1179267 --- /dev/null +++ b/example-expo/app/characteristic.tsx @@ -0,0 +1,284 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + TextInput, + Switch, + StyleSheet, + Alert, + ScrollView, + Platform, +} from 'react-native'; +import { useLocalSearchParams } from 'expo-router'; +import type { Subscription } from 'react-native-ble-plx'; +import { useBleManager } from './_layout'; + +function base64ByteLength(b64: string): number { + const padding = (b64.match(/=+$/) || [''])[0].length; + return Math.floor((b64.length * 3) / 4) - padding; +} + +export default function CharacteristicScreen() { + const manager = useBleManager(); + const params = useLocalSearchParams<{ + deviceId: string; + serviceUuid: string; + characteristicUuid: string; + properties: string; + }>(); + + const { deviceId, serviceUuid, characteristicUuid } = params; + const properties = JSON.parse(params.properties || '{}') as { + isReadable: boolean; + isWritableWithResponse: boolean; + isWritableWithoutResponse: boolean; + isNotifying: boolean; + isIndicatable: boolean; + }; + + const [value, setValue] = useState('(none)'); + const [writeValue, setWriteValue] = useState(''); + const [monitoring, setMonitoring] = useState(false); + const [sampleCount, setSampleCount] = useState(0); + const [distinctCount, setDistinctCount] = useState(0); + const [stoppedAtCount, setStoppedAtCount] = useState(null); + const [writeError, setWriteError] = useState(null); + const [payloadLength, setPayloadLength] = useState(null); + const monitorSub = useRef(null); + const sampleCountRef = useRef(0); + const seenValues = useRef>(new Set()); + + useEffect(() => { + return () => { + monitorSub.current?.remove(); + }; + }, []); + + const readCharacteristic = useCallback(async () => { + try { + const result = await manager.readCharacteristicForDevice( + deviceId!, + serviceUuid!, + characteristicUuid!, + ); + const readValue = result.value ?? '(null)'; + setValue(readValue); + if (result.value) { + setPayloadLength(base64ByteLength(result.value)); + } + } catch (e: any) { + Alert.alert('Read Error', e.message || String(e)); + } + }, [manager, deviceId, serviceUuid, characteristicUuid]); + + const writeCharacteristic = useCallback(async () => { + try { + await manager.writeCharacteristicForDevice( + deviceId!, + serviceUuid!, + characteristicUuid!, + writeValue, + properties.isWritableWithResponse, + ); + setWriteError(null); + Alert.alert('Write', 'Value written successfully'); + } catch (e: any) { + setWriteError(e.message || String(e)); + Alert.alert('Write Error', e.message || String(e)); + } + }, [manager, deviceId, serviceUuid, characteristicUuid, writeValue, properties]); + + const toggleMonitor = useCallback( + (enabled: boolean) => { + if (enabled) { + // Reset counters + setSampleCount(0); + setDistinctCount(0); + setStoppedAtCount(null); + sampleCountRef.current = 0; + seenValues.current = new Set(); + + monitorSub.current = manager.monitorCharacteristicForDevice( + deviceId!, + serviceUuid!, + characteristicUuid!, + (error: any, event: any) => { + if (error) { + console.warn('Monitor error:', error); + setMonitoring(false); + return; + } + if (event) { + setValue(event.value); + sampleCountRef.current += 1; + setSampleCount((c) => c + 1); + if (!seenValues.current.has(event.value)) { + seenValues.current.add(event.value); + setDistinctCount((c) => c + 1); + } + } + }, + ); + setMonitoring(true); + } else { + monitorSub.current?.remove(); + monitorSub.current = null; + setStoppedAtCount(sampleCountRef.current); + setMonitoring(false); + } + }, + [manager, deviceId, serviceUuid, characteristicUuid], + ); + + return ( + + Characteristic + {characteristicUuid} + Service: {serviceUuid} + + + Properties: + + {[ + properties.isReadable && 'Readable', + properties.isWritableWithResponse && 'Writable (response)', + properties.isWritableWithoutResponse && 'Writable (no response)', + properties.isNotifying && 'Notifiable', + properties.isIndicatable && 'Indicatable', + ] + .filter(Boolean) + .join(', ') || 'None'} + + + + + + Value: {value} + + + + {payloadLength != null && ( + + Payload: {payloadLength} bytes + + )} + + {/* E2E instrumentation: sample and distinct counts for monitor assertions */} + {(properties.isNotifying || properties.isIndicatable) && ( + + + Samples: {sampleCount} + + + Distinct: {distinctCount} + + + )} + + {stoppedAtCount != null && ( + + {sampleCount <= stoppedAtCount + 1 ? 'Monitor: STOPPED' : 'Monitor: LEAKED'} + + )} + + {properties.isReadable && ( + + Read + + )} + + {(properties.isWritableWithResponse || + properties.isWritableWithoutResponse) && ( + + + + setWriteValue('QQ==')}> + 1 byte + + setWriteValue('QUJDREVGR0hJSktMTU5PUFFSU1Q=')}> + 20 bytes + + + + Write + + {writeError && ( + + {writeError} + + )} + + )} + + {(properties.isNotifying || properties.isIndicatable) && ( + + Monitor + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#fff' }, + content: { padding: 16 }, + title: { fontSize: 20, fontWeight: '700' }, + uuid: { fontSize: 13, color: '#888', marginTop: 4 }, + service: { fontSize: 12, color: '#aaa', marginTop: 2 }, + propsRow: { marginTop: 12 }, + propLabel: { fontSize: 14, fontWeight: '600' }, + propValue: { fontSize: 13, color: '#555', marginTop: 2 }, + section: { marginTop: 16, marginBottom: 12 }, + valueText: { fontSize: 16, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace' }, + countersRow: { flexDirection: 'row', gap: 16, marginBottom: 12 }, + counterText: { fontSize: 13, color: '#666', fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace' }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + alignSelf: 'flex-start', + marginTop: 8, + }, + buttonText: { color: '#fff', fontWeight: '600' }, + writeSection: { marginTop: 12 }, + textInput: { + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + fontSize: 14, + }, + presetRow: { flexDirection: 'row', gap: 8, marginTop: 8 }, + presetButton: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6, borderWidth: 1, borderColor: '#007AFF', backgroundColor: '#E8F0FE' }, + presetText: { fontSize: 12, color: '#007AFF', fontWeight: '600' }, + monitorRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 16, + paddingVertical: 8, + }, + monitorLabel: { fontSize: 16, fontWeight: '600' }, + errorText: { fontSize: 13, color: '#FF3B30', marginTop: 8 }, +}); diff --git a/example-expo/app/device.tsx b/example-expo/app/device.tsx new file mode 100644 index 000000000..2c56d81a1 --- /dev/null +++ b/example-expo/app/device.tsx @@ -0,0 +1,330 @@ +import { useState, useCallback, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + FlatList, + StyleSheet, + Alert, + TextInput, + Platform, +} from 'react-native'; +import { useLocalSearchParams, useRouter } from 'expo-router'; +import { useBleManager } from './_layout'; + +// Known test peripheral characteristics for quick access +const TEST_SERVICE_UUID = '12345678-1234-1234-1234-123456789ABC'; +const TEST_CHARACTERISTICS: { + uuid: string; + testID: string; + label: string; + props: { + isReadable: boolean; + isWritableWithResponse: boolean; + isWritableWithoutResponse: boolean; + isNotifying: boolean; + isIndicatable: boolean; + }; +}[] = [ + { uuid: '12345678-1234-1234-1234-123456789A01', testID: 'char-read-counter', label: 'Read Counter', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A02', testID: 'char-write-echo', label: 'Write Echo', props: { isReadable: true, isWritableWithResponse: true, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A03', testID: 'char-notify-stream', label: 'Notify Stream', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: true, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A04', testID: 'char-indicate-stream', label: 'Indicate Stream', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: true } }, + { uuid: '12345678-1234-1234-1234-123456789A05', testID: 'char-mtu-test', label: 'MTU Test', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A06', testID: 'char-write-no-response', label: 'Write No Response', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: true, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A07', testID: 'char-l2cap-psm', label: 'L2CAP PSM', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, +]; + +export default function DeviceScreen() { + const manager = useBleManager(); + const router = useRouter(); + const { deviceId, deviceName } = useLocalSearchParams<{ + deviceId: string; + deviceName: string; + }>(); + const [mtu, setMtu] = useState(null); + const [serviceUuids, setServiceUuids] = useState([]); + const [discovering, setDiscovering] = useState(false); + const [customCharUuid, setCustomCharUuid] = useState(''); + const [selectedService, setSelectedService] = useState(null); + const [mtuRequestStatus, setMtuRequestStatus] = useState(null); + + useEffect(() => { + if (deviceId) { + manager.getMtu(deviceId).then(setMtu).catch(() => {}); + } + }, [manager, deviceId]); + + const discoverServices = useCallback(async () => { + if (!deviceId) return; + setDiscovering(true); + try { + const info = await manager.discoverAllServicesAndCharacteristics(deviceId); + const uuids = (info.serviceUuids as string[]) || []; + setServiceUuids(uuids); + // Auto-expand the test service if found + const testSvc = uuids.find((u) => u.toUpperCase() === TEST_SERVICE_UUID); + if (testSvc) { + setSelectedService(testSvc); + } + } catch (e: any) { + Alert.alert('Discovery Error', e.message || String(e)); + } finally { + setDiscovering(false); + } + }, [manager, deviceId]); + + const requestMtu = useCallback(async () => { + if (!deviceId) return; + try { + await manager.requestMTUForDevice(deviceId, 247); + const newMtu = await manager.getMtu(deviceId); + setMtu(newMtu); + setMtuRequestStatus(`Success: MTU is now ${newMtu}`); + } catch (e: any) { + setMtuRequestStatus(`Error: ${e.message || String(e)}`); + } + }, [manager, deviceId]); + + const disconnect = useCallback(async () => { + if (!deviceId) return; + try { + await manager.cancelDeviceConnection(deviceId); + router.back(); + } catch (e: any) { + Alert.alert('Disconnect Error', e.message || String(e)); + } + }, [manager, deviceId, router]); + + const navigateToCharacteristic = useCallback( + (serviceUuid: string, charUuid: string, props: { + isReadable: boolean; + isWritableWithResponse: boolean; + isWritableWithoutResponse: boolean; + isNotifying: boolean; + isIndicatable: boolean; + }) => { + router.push({ + pathname: '/characteristic', + params: { + deviceId: deviceId!, + serviceUuid, + characteristicUuid: charUuid, + properties: JSON.stringify(props), + }, + }); + }, + [deviceId, router], + ); + + const L2CAP_PSM_UUID = '12345678-1234-1234-1234-123456789A07'; + + const openL2CAP = useCallback(async (serviceUuid: string) => { + if (!deviceId) return; + try { + const result = await manager.readCharacteristicForDevice( + deviceId, serviceUuid, L2CAP_PSM_UUID, + ); + if (!result.value) { + Alert.alert('L2CAP Error', 'PSM characteristic returned no value'); + return; + } + // Decode base64 uint16 LE + const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + const raw = result.value.replace(/=/g, ''); + const decoded: number[] = []; + for (let i = 0; i < raw.length; i += 4) { + const a = b64.indexOf(raw[i]); + const b = b64.indexOf(raw[i + 1] || 'A'); + const c = b64.indexOf(raw[i + 2] || 'A'); + const d = b64.indexOf(raw[i + 3] || 'A'); + decoded.push((a << 2) | (b >> 4)); + if (raw[i + 2]) decoded.push(((b & 15) << 4) | (c >> 2)); + if (raw[i + 3]) decoded.push(((c & 3) << 6) | d); + } + const psm = decoded[0] | ((decoded[1] || 0) << 8); + if (psm === 0) { + Alert.alert('L2CAP Error', 'PSM is 0 -- L2CAP server not ready'); + return; + } + router.push({ + pathname: '/l2cap', + params: { deviceId, psm: String(psm) }, + }); + } catch (e: any) { + Alert.alert('L2CAP Error', e.message || String(e)); + } + }, [manager, deviceId, router]); + + const isTestService = (uuid: string) => + uuid.toUpperCase() === TEST_SERVICE_UUID; + + return ( + + + {deviceName || 'Unknown Device'} + ID: {deviceId} + {mtu != null && MTU: {mtu}} + + + + + + {discovering ? 'Discovering...' : 'Discover Services'} + + + + + Disconnect + + + + {Platform.OS === 'android' && ( + + + Request MTU 247 + + {mtuRequestStatus != null && ( + + {mtuRequestStatus} + + )} + + )} + + {serviceUuids.length > 0 && ( + item} + renderItem={({ item: svcUuid }) => ( + + setSelectedService(selectedService === svcUuid ? null : svcUuid)} + style={styles.serviceHeader}> + + {selectedService === svcUuid ? '▼' : '▶'} Service: {svcUuid} + + {isTestService(svcUuid) && ( + TEST + )} + + + {selectedService === svcUuid && isTestService(svcUuid) && ( + + {TEST_CHARACTERISTICS.map(({ uuid: charUuid, testID, label, props }) => ( + charUuid.toUpperCase().endsWith('9A07') + ? openL2CAP(svcUuid) + : navigateToCharacteristic(svcUuid, charUuid, props)}> + {label} + {charUuid} + + {[ + props.isReadable && 'Read', + props.isWritableWithResponse && 'Write', + props.isNotifying && 'Notify', + props.isIndicatable && 'Indicate', + ] + .filter(Boolean) + .join(', ')} + + + ))} + + )} + + {selectedService === svcUuid && !isTestService(svcUuid) && ( + + + Enter a characteristic UUID to interact with: + + + + navigateToCharacteristic(svcUuid, customCharUuid, { + isReadable: true, + isWritableWithResponse: true, + isWritableWithoutResponse: false, + isNotifying: true, + isIndicatable: true, + }) + }> + Open + + + )} + + )} + /> + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, backgroundColor: '#fff' }, + infoSection: { marginBottom: 16 }, + deviceName: { fontSize: 20, fontWeight: '700' }, + deviceId: { fontSize: 13, color: '#888', marginTop: 4 }, + mtuText: { fontSize: 13, color: '#888', marginTop: 2 }, + buttonRow: { flexDirection: 'row', gap: 12, marginBottom: 16 }, + mtuRequestSection: { marginBottom: 16 }, + mtuStatusText: { fontSize: 13, color: '#333', marginTop: 8 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + buttonDisabled: { backgroundColor: '#ccc' }, + disconnectButton: { backgroundColor: '#FF3B30' }, + buttonText: { color: '#fff', fontWeight: '600' }, + serviceGroup: { marginBottom: 12, borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, overflow: 'hidden' }, + serviceHeader: { flexDirection: 'row', alignItems: 'center', padding: 12, backgroundColor: '#f8f8f8' }, + serviceUuid: { fontSize: 13, fontWeight: '600', flex: 1 }, + testBadge: { fontSize: 10, fontWeight: '700', color: '#007AFF', backgroundColor: '#E8F0FE', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, overflow: 'hidden' }, + charList: { padding: 12 }, + charRow: { + paddingVertical: 10, + paddingHorizontal: 8, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + charLabel: { fontSize: 14, fontWeight: '600' }, + charUuid: { fontSize: 11, color: '#888', marginTop: 2 }, + charProps: { fontSize: 11, color: '#007AFF', marginTop: 2 }, + unknownNote: { fontSize: 13, color: '#666', marginBottom: 8 }, + textInput: { + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + fontSize: 14, + marginBottom: 8, + }, +}); diff --git a/example-expo/app/index.tsx b/example-expo/app/index.tsx new file mode 100644 index 000000000..6525ee171 --- /dev/null +++ b/example-expo/app/index.tsx @@ -0,0 +1,239 @@ +import { useEffect, useState, useCallback, useRef } from 'react'; +import { + View, + Text, + TouchableOpacity, + Pressable, + TextInput, + StyleSheet, + Platform, + PermissionsAndroid, + Alert, +} from 'react-native'; +import { useRouter } from 'expo-router'; +import type { ScanResult } from 'react-native-ble-plx'; +import { useBleManager } from './_layout'; + +async function requestAndroidPermissions(): Promise { + if (Platform.OS !== 'android') return true; + + try { + const granted = await PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, + PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, + PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, + ]); + return Object.values(granted).every( + (v) => v === PermissionsAndroid.RESULTS.GRANTED, + ); + } catch { + return false; + } +} + +export default function ScanScreen() { + const manager = useBleManager(); + const router = useRouter(); + const [scanning, setScanning] = useState(false); + const [bleState, setBleState] = useState('Unknown'); + const [devices, setDevices] = useState>(new Map()); + const [filterEnabled, setFilterEnabled] = useState(false); + const [searchText, setSearchText] = useState(''); + const [connectionStatus, setConnectionStatus] = useState(''); + const [scanError, setScanError] = useState(null); + const devicesRef = useRef>(new Map()); + + const TEST_SERVICE_UUID = '12345678-1234-1234-1234-123456789ABC'; + + useEffect(() => { + const sub = manager.onStateChange((state: string) => { + setBleState(state); + }, true); + + requestAndroidPermissions().then((ok) => { + if (!ok) { + setScanError('BLE permissions not granted'); + Alert.alert('Permissions', 'BLE permissions not granted'); + } + }); + + return () => sub.remove(); + }, [manager]); + + const startScan = useCallback(() => { + manager.stopDeviceScan(); + devicesRef.current = new Map(); + setDevices(new Map()); + setScanning(true); + setConnectionStatus(''); + setScanError(null); + + const uuids = filterEnabled ? [TEST_SERVICE_UUID] : null; + manager.startDeviceScan(uuids, null, (error: any, result: ScanResult | null) => { + if (error) { + console.warn('Scan error:', error); + setScanError(error.message || String(error)); + setScanning(false); + return; + } + if (result) { + devicesRef.current.set(result.id, result); + setDevices(new Map(devicesRef.current)); + } + }); + }, [manager, filterEnabled]); + + const stopScan = useCallback(() => { + manager.stopDeviceScan(); + setScanning(false); + }, [manager]); + + const connectToDevice = useCallback( + async (device: ScanResult) => { + setConnectionStatus(`Connecting to ${device.name || device.id}...`); + stopScan(); + try { + // Check if already connected -- if so, just navigate + const connected = await manager.isDeviceConnected(device.id); + if (connected) { + setConnectionStatus('Already connected, navigating...'); + router.push({ + pathname: '/device', + params: { deviceId: device.id, deviceName: device.name ?? '' }, + }); + return; + } + const result = await manager.connectToDevice(device.id); + setConnectionStatus('Connected!'); + router.push({ + pathname: '/device', + params: { deviceId: result.id, deviceName: result.name ?? '' }, + }); + } catch (e: any) { + setConnectionStatus(`Error: ${e.message || String(e)}`); + Alert.alert('Connection Error', e.message || String(e)); + } + }, + [manager, router, stopScan], + ); + + const deviceList = Array.from(devices.values()) + .filter((d) => { + if (!searchText) return true; + const q = searchText.toLowerCase(); + return d.name?.toLowerCase().includes(q) || d.id.toLowerCase().includes(q); + }) + .sort((a, b) => (b.rssi ?? -999) - (a.rssi ?? -999)); + + return ( + + + BLE Scanner ({Platform.OS}) + + BLE State: {bleState} + + {connectionStatus ? ( + {connectionStatus} + ) : null} + + {scanError && ( + + {scanError} + + )} + + + + Start Scan + + + Stop Scan + + + + + + setFilterEnabled(!filterEnabled)}> + + {filterEnabled ? 'Filter: Test Service UUID' : 'Filter: None'} + + + + + {deviceList.length} device(s) found + + + + {deviceList.map((item) => ( + [ + styles.deviceRow, + pressed && styles.deviceRowPressed, + ]} + onPress={() => connectToDevice(item)}> + + {item.name || 'Unknown Device'} + + {item.id} + RSSI: {item.rssi} + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, backgroundColor: '#fff' }, + titleText: { fontSize: 20, fontWeight: '700', marginBottom: 4 }, + stateText: { fontSize: 16, fontWeight: '600', marginBottom: 8 }, + statusText: { fontSize: 14, fontWeight: '500', color: '#007AFF', marginBottom: 8, padding: 8, backgroundColor: '#E8F0FE', borderRadius: 6 }, + buttonRow: { flexDirection: 'row', gap: 12, marginBottom: 12 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + buttonDisabled: { backgroundColor: '#ccc' }, + buttonText: { color: '#fff', fontWeight: '600' }, + filterButton: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 6, borderWidth: 1, borderColor: '#ccc', alignSelf: 'flex-start', marginBottom: 8 }, + filterActive: { borderColor: '#007AFF', backgroundColor: '#E8F0FE' }, + filterText: { fontSize: 12, color: '#888' }, + filterTextActive: { color: '#007AFF', fontWeight: '600' }, + searchInput: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10, fontSize: 14, marginBottom: 8 }, + countText: { fontSize: 14, color: '#666', marginBottom: 8 }, + deviceRow: { + padding: 12, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + deviceRowPressed: { + backgroundColor: '#f0f0f0', + }, + deviceName: { fontSize: 16, fontWeight: '500' }, + deviceId: { fontSize: 12, color: '#888', marginTop: 2 }, + deviceRssi: { fontSize: 12, color: '#888', marginTop: 2 }, + errorText: { fontSize: 14, color: '#FF3B30', marginBottom: 8, padding: 8, backgroundColor: '#FDE8E8', borderRadius: 6 }, +}); diff --git a/example-expo/app/l2cap.tsx b/example-expo/app/l2cap.tsx new file mode 100644 index 000000000..d0bedd758 --- /dev/null +++ b/example-expo/app/l2cap.tsx @@ -0,0 +1,186 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + TextInput, + StyleSheet, + Alert, + ScrollView, + Platform, +} from 'react-native'; +import { useLocalSearchParams } from 'expo-router'; +import { useBleManager } from './_layout'; + +export default function L2CAPScreen() { + const manager = useBleManager(); + const params = useLocalSearchParams<{ deviceId: string; psm: string }>(); + const deviceId = params.deviceId!; + const psm = Number(params.psm); + + const [channelId, setChannelId] = useState(null); + const [writeData, setWriteData] = useState(''); + const [status, setStatus] = useState('Not connected'); + const [lastError, setLastError] = useState(null); + const [rxData, setRxData] = useState(null); + const channelIdRef = useRef(null); + const rxSub = useRef(null); + + useEffect(() => { + return () => { + rxSub.current?.remove(); + rxSub.current = null; + if (channelIdRef.current != null) { + manager.closeL2CAPChannel(channelIdRef.current).catch(() => {}); + } + }; + }, [manager]); + + const openChannel = useCallback(async () => { + try { + setStatus('Opening...'); + setLastError(null); + const result = await manager.openL2CAPChannel(deviceId, psm); + setChannelId(result.channelId); + channelIdRef.current = result.channelId; + setStatus(`Connected (channel ${result.channelId})`); + rxSub.current = manager.monitorL2CAPChannel(result.channelId, (_error: any, data: any) => { + if (data?.data) { + setRxData(data.data); + } + }); + } catch (e: any) { + setLastError(e.message || String(e)); + setStatus('Failed'); + Alert.alert('L2CAP Error', e.message || String(e)); + } + }, [manager, deviceId, psm]); + + const writeChannel = useCallback(async () => { + if (channelId == null) return; + try { + setLastError(null); + await manager.writeL2CAPChannel(channelId, writeData); + setStatus(`Wrote ${writeData.length} chars`); + } catch (e: any) { + setLastError(e.message || String(e)); + Alert.alert('Write Error', e.message || String(e)); + } + }, [manager, channelId, writeData]); + + const closeChannel = useCallback(async () => { + if (channelId == null) return; + const closingId = channelId; + channelIdRef.current = null; // Prevent double-close from useEffect + setChannelId(null); + try { + await manager.closeL2CAPChannel(closingId); + rxSub.current?.remove(); + rxSub.current = null; + setRxData(null); + setStatus('Closed'); + } catch (e: any) { + // Channel close failed -- still clean up subscription + rxSub.current?.remove(); + rxSub.current = null; + setLastError(e.message || String(e)); + setStatus('Close failed'); + } + }, [manager, channelId]); + + return ( + + L2CAP Channel + PSM: {psm} + Device: {deviceId} + + + Status: {status} + + + {lastError && ( + + Error: {lastError} + + )} + + {channelId == null ? ( + + Open Channel + + ) : ( + <> + + Channel ID: {channelId} + + + + + + Write + + + + {rxData != null && ( + + Received: {rxData} + + )} + + + Close Channel + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#fff' }, + content: { padding: 16 }, + title: { fontSize: 20, fontWeight: '700' }, + info: { fontSize: 13, color: '#888', marginTop: 4 }, + statusText: { + fontSize: 16, + fontWeight: '600', + marginTop: 16, + marginBottom: 12, + fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + }, + errorText: { fontSize: 13, color: '#FF3B30', marginBottom: 8 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + alignSelf: 'flex-start', + marginTop: 8, + }, + closeButton: { backgroundColor: '#FF3B30' }, + buttonText: { color: '#fff', fontWeight: '600' }, + writeSection: { marginTop: 12 }, + textInput: { + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + fontSize: 14, + marginBottom: 8, + }, +}); diff --git a/example-expo/assets/adaptive-icon.png b/example-expo/assets/adaptive-icon.png deleted file mode 100644 index 03d6f6b6c..000000000 Binary files a/example-expo/assets/adaptive-icon.png and /dev/null differ diff --git a/example-expo/assets/expo.icon/Assets/expo-symbol 2.svg b/example-expo/assets/expo.icon/Assets/expo-symbol 2.svg new file mode 100644 index 000000000..51d367673 --- /dev/null +++ b/example-expo/assets/expo.icon/Assets/expo-symbol 2.svg @@ -0,0 +1,3 @@ + + + diff --git a/example-expo/assets/expo.icon/Assets/grid.png b/example-expo/assets/expo.icon/Assets/grid.png new file mode 100644 index 000000000..eefea2424 Binary files /dev/null and b/example-expo/assets/expo.icon/Assets/grid.png differ diff --git a/example-expo/assets/expo.icon/icon.json b/example-expo/assets/expo.icon/icon.json new file mode 100644 index 000000000..7a2c33cd0 --- /dev/null +++ b/example-expo/assets/expo.icon/icon.json @@ -0,0 +1,40 @@ +{ + "fill" : { + "automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "image-name" : "expo-symbol 2.svg", + "name" : "expo-symbol 2", + "position" : { + "scale" : 1, + "translation-in-points" : [ + 1.1008400065293245e-05, + -16.046875 + ] + } + }, + { + "image-name" : "grid.png", + "name" : "grid" + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/example-expo/assets/favicon.png b/example-expo/assets/favicon.png deleted file mode 100644 index e75f697b1..000000000 Binary files a/example-expo/assets/favicon.png and /dev/null differ diff --git a/example-expo/assets/icon.png b/example-expo/assets/icon.png deleted file mode 100644 index a0b1526fc..000000000 Binary files a/example-expo/assets/icon.png and /dev/null differ diff --git a/example-expo/assets/images/android-icon-background.png b/example-expo/assets/images/android-icon-background.png new file mode 100644 index 000000000..5ffefc5bb Binary files /dev/null and b/example-expo/assets/images/android-icon-background.png differ diff --git a/example-expo/assets/images/android-icon-foreground.png b/example-expo/assets/images/android-icon-foreground.png new file mode 100644 index 000000000..3a9e5016d Binary files /dev/null and b/example-expo/assets/images/android-icon-foreground.png differ diff --git a/example-expo/assets/images/android-icon-monochrome.png b/example-expo/assets/images/android-icon-monochrome.png new file mode 100644 index 000000000..77484ebdb Binary files /dev/null and b/example-expo/assets/images/android-icon-monochrome.png differ diff --git a/example-expo/assets/images/expo-badge-white.png b/example-expo/assets/images/expo-badge-white.png new file mode 100644 index 000000000..28630679f Binary files /dev/null and b/example-expo/assets/images/expo-badge-white.png differ diff --git a/example-expo/assets/images/expo-badge.png b/example-expo/assets/images/expo-badge.png new file mode 100644 index 000000000..5d5c5bb5c Binary files /dev/null and b/example-expo/assets/images/expo-badge.png differ diff --git a/example-expo/assets/images/expo-logo.png b/example-expo/assets/images/expo-logo.png new file mode 100644 index 000000000..6b1642a0b Binary files /dev/null and b/example-expo/assets/images/expo-logo.png differ diff --git a/example-expo/assets/images/favicon.png b/example-expo/assets/images/favicon.png new file mode 100644 index 000000000..408bd7466 Binary files /dev/null and b/example-expo/assets/images/favicon.png differ diff --git a/example-expo/assets/images/icon.png b/example-expo/assets/images/icon.png new file mode 100644 index 000000000..6648b56b5 Binary files /dev/null and b/example-expo/assets/images/icon.png differ diff --git a/example-expo/assets/images/logo-glow.png b/example-expo/assets/images/logo-glow.png new file mode 100644 index 000000000..edc99be1b Binary files /dev/null and b/example-expo/assets/images/logo-glow.png differ diff --git a/example-expo/assets/images/react-logo.png b/example-expo/assets/images/react-logo.png new file mode 100644 index 000000000..9d72a9ffc Binary files /dev/null and b/example-expo/assets/images/react-logo.png differ diff --git a/example-expo/assets/images/react-logo@2x.png b/example-expo/assets/images/react-logo@2x.png new file mode 100644 index 000000000..2229b130a Binary files /dev/null and b/example-expo/assets/images/react-logo@2x.png differ diff --git a/example-expo/assets/images/react-logo@3x.png b/example-expo/assets/images/react-logo@3x.png new file mode 100644 index 000000000..a99b20322 Binary files /dev/null and b/example-expo/assets/images/react-logo@3x.png differ diff --git a/example-expo/assets/images/splash-icon.png b/example-expo/assets/images/splash-icon.png new file mode 100644 index 000000000..6b1642a0b Binary files /dev/null and b/example-expo/assets/images/splash-icon.png differ diff --git a/example-expo/assets/images/tabIcons/explore.png b/example-expo/assets/images/tabIcons/explore.png new file mode 100644 index 000000000..73d825834 Binary files /dev/null and b/example-expo/assets/images/tabIcons/explore.png differ diff --git a/example-expo/assets/images/tabIcons/explore@2x.png b/example-expo/assets/images/tabIcons/explore@2x.png new file mode 100644 index 000000000..21b9bd266 Binary files /dev/null and b/example-expo/assets/images/tabIcons/explore@2x.png differ diff --git a/example-expo/assets/images/tabIcons/explore@3x.png b/example-expo/assets/images/tabIcons/explore@3x.png new file mode 100644 index 000000000..422202d5e Binary files /dev/null and b/example-expo/assets/images/tabIcons/explore@3x.png differ diff --git a/example-expo/assets/images/tabIcons/home.png b/example-expo/assets/images/tabIcons/home.png new file mode 100644 index 000000000..ad5699c42 Binary files /dev/null and b/example-expo/assets/images/tabIcons/home.png differ diff --git a/example-expo/assets/images/tabIcons/home@2x.png b/example-expo/assets/images/tabIcons/home@2x.png new file mode 100644 index 000000000..22a1f2c74 Binary files /dev/null and b/example-expo/assets/images/tabIcons/home@2x.png differ diff --git a/example-expo/assets/images/tabIcons/home@3x.png b/example-expo/assets/images/tabIcons/home@3x.png new file mode 100644 index 000000000..f5d1f9a41 Binary files /dev/null and b/example-expo/assets/images/tabIcons/home@3x.png differ diff --git a/example-expo/assets/images/tutorial-web.png b/example-expo/assets/images/tutorial-web.png new file mode 100644 index 000000000..e4a8c58f7 Binary files /dev/null and b/example-expo/assets/images/tutorial-web.png differ diff --git a/example-expo/assets/splash.png b/example-expo/assets/splash.png deleted file mode 100644 index 0e89705a9..000000000 Binary files a/example-expo/assets/splash.png and /dev/null differ diff --git a/example-expo/babel.config.js b/example-expo/babel.config.js deleted file mode 100644 index d799d652a..000000000 --- a/example-expo/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function (api) { - api.cache(true) - return { - presets: ['babel-preset-expo'] - } -} diff --git a/example-expo/ios/.gitignore b/example-expo/ios/.gitignore deleted file mode 100644 index 8beb34430..000000000 --- a/example-expo/ios/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -project.xcworkspace -.xcode.env.local - -# Bundle artifacts -*.jsbundle - -# CocoaPods -/Pods/ diff --git a/example-expo/ios/.xcode.env b/example-expo/ios/.xcode.env deleted file mode 100644 index 3d5782c71..000000000 --- a/example-expo/ios/.xcode.env +++ /dev/null @@ -1,11 +0,0 @@ -# This `.xcode.env` file is versioned and is used to source the environment -# used when running script phases inside Xcode. -# To customize your local environment, you can create an `.xcode.env.local` -# file that is not versioned. - -# NODE_BINARY variable contains the PATH to the node executable. -# -# Customize the NODE_BINARY variable here. -# For example, to use nvm with brew, add the following line -# . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) diff --git a/example-expo/ios/Podfile b/example-expo/ios/Podfile deleted file mode 100644 index 01e4f5bf9..000000000 --- a/example-expo/ios/Podfile +++ /dev/null @@ -1,58 +0,0 @@ -require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") -require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") - -require 'json' -podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} - -ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' -ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] - -platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4' -install! 'cocoapods', - :deterministic_uuids => false - -prepare_react_native_project! - -target 'exampleexpo' do - use_expo_modules! - config = use_native_modules! - - use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] - use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] - - use_react_native!( - :path => config[:reactNativePath], - :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', - # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/..", - :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', - ) - - post_install do |installer| - react_native_post_install( - installer, - config[:reactNativePath], - :mac_catalyst_enabled => false, - :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true', - ) - - # This is necessary for Xcode 14, because it signs resource bundles by default - # when building for devices. - installer.target_installation_results.pod_target_installation_results - .each do |pod_name, target_installation_result| - target_installation_result.resource_bundle_targets.each do |resource_bundle_target| - resource_bundle_target.build_configurations.each do |config| - config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' - end - end - end - end - - post_integrate do |installer| - begin - expo_patch_react_imports!(installer) - rescue => e - Pod::UI.warn e - end - end -end diff --git a/example-expo/ios/Podfile.lock b/example-expo/ios/Podfile.lock deleted file mode 100644 index d24d4358f..000000000 --- a/example-expo/ios/Podfile.lock +++ /dev/null @@ -1,1526 +0,0 @@ -PODS: - - boost (1.83.0) - - DoubleConversion (1.1.6) - - EXConstants (16.0.2): - - ExpoModulesCore - - Expo (51.0.14): - - ExpoModulesCore - - ExpoAsset (10.0.9): - - ExpoModulesCore - - ExpoFileSystem (17.0.1): - - ExpoModulesCore - - ExpoFont (12.0.7): - - ExpoModulesCore - - ExpoKeepAwake (13.0.2): - - ExpoModulesCore - - ExpoModulesCore (1.12.15): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsinspector - - React-NativeModulesApple - - React-RCTAppDelegate - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - FBLazyVector (0.74.2) - - fmt (9.1.0) - - glog (0.3.5) - - hermes-engine (0.74.2): - - hermes-engine/Pre-built (= 0.74.2) - - hermes-engine/Pre-built (0.74.2) - - MultiplatformBleAdapter (0.2.0) - - RCT-Folly (2024.01.01.00): - - boost - - DoubleConversion - - fmt (= 9.1.0) - - glog - - RCT-Folly/Default (= 2024.01.01.00) - - RCT-Folly/Default (2024.01.01.00): - - boost - - DoubleConversion - - fmt (= 9.1.0) - - glog - - RCT-Folly/Fabric (2024.01.01.00): - - boost - - DoubleConversion - - fmt (= 9.1.0) - - glog - - RCTDeprecation (0.74.2) - - RCTRequired (0.74.2) - - RCTTypeSafety (0.74.2): - - FBLazyVector (= 0.74.2) - - RCTRequired (= 0.74.2) - - React-Core (= 0.74.2) - - React (0.74.2): - - React-Core (= 0.74.2) - - React-Core/DevSupport (= 0.74.2) - - React-Core/RCTWebSocket (= 0.74.2) - - React-RCTActionSheet (= 0.74.2) - - React-RCTAnimation (= 0.74.2) - - React-RCTBlob (= 0.74.2) - - React-RCTImage (= 0.74.2) - - React-RCTLinking (= 0.74.2) - - React-RCTNetwork (= 0.74.2) - - React-RCTSettings (= 0.74.2) - - React-RCTText (= 0.74.2) - - React-RCTVibration (= 0.74.2) - - React-callinvoker (0.74.2) - - React-Codegen (0.74.2): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-FabricImage - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-NativeModulesApple - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - React-Core (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default (= 0.74.2) - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/CoreModulesHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/Default (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/DevSupport (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default (= 0.74.2) - - React-Core/RCTWebSocket (= 0.74.2) - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTActionSheetHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTAnimationHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTBlobHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTImageHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTLinkingHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTNetworkHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTSettingsHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTTextHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTVibrationHeaders (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-Core/RCTWebSocket (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTDeprecation - - React-Core/Default (= 0.74.2) - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-perflogger - - React-runtimescheduler - - React-utils - - SocketRocket (= 0.7.0) - - Yoga - - React-CoreModules (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - RCT-Folly (= 2024.01.01.00) - - RCTTypeSafety (= 0.74.2) - - React-Codegen - - React-Core/CoreModulesHeaders (= 0.74.2) - - React-jsi (= 0.74.2) - - React-jsinspector - - React-NativeModulesApple - - React-RCTBlob - - React-RCTImage (= 0.74.2) - - ReactCommon - - SocketRocket (= 0.7.0) - - React-cxxreact (0.74.2): - - boost (= 1.83.0) - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.74.2) - - React-debug (= 0.74.2) - - React-jsi (= 0.74.2) - - React-jsinspector - - React-logger (= 0.74.2) - - React-perflogger (= 0.74.2) - - React-runtimeexecutor (= 0.74.2) - - React-debug (0.74.2) - - React-Fabric (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/animations (= 0.74.2) - - React-Fabric/attributedstring (= 0.74.2) - - React-Fabric/componentregistry (= 0.74.2) - - React-Fabric/componentregistrynative (= 0.74.2) - - React-Fabric/components (= 0.74.2) - - React-Fabric/core (= 0.74.2) - - React-Fabric/imagemanager (= 0.74.2) - - React-Fabric/leakchecker (= 0.74.2) - - React-Fabric/mounting (= 0.74.2) - - React-Fabric/scheduler (= 0.74.2) - - React-Fabric/telemetry (= 0.74.2) - - React-Fabric/templateprocessor (= 0.74.2) - - React-Fabric/textlayoutmanager (= 0.74.2) - - React-Fabric/uimanager (= 0.74.2) - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/animations (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/attributedstring (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/componentregistry (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/componentregistrynative (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/components/inputaccessory (= 0.74.2) - - React-Fabric/components/legacyviewmanagerinterop (= 0.74.2) - - React-Fabric/components/modal (= 0.74.2) - - React-Fabric/components/rncore (= 0.74.2) - - React-Fabric/components/root (= 0.74.2) - - React-Fabric/components/safeareaview (= 0.74.2) - - React-Fabric/components/scrollview (= 0.74.2) - - React-Fabric/components/text (= 0.74.2) - - React-Fabric/components/textinput (= 0.74.2) - - React-Fabric/components/unimplementedview (= 0.74.2) - - React-Fabric/components/view (= 0.74.2) - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/inputaccessory (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/legacyviewmanagerinterop (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/modal (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/rncore (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/root (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/safeareaview (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/scrollview (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/text (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/textinput (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/unimplementedview (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/components/view (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - Yoga - - React-Fabric/core (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/imagemanager (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/leakchecker (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/mounting (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/scheduler (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/telemetry (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/templateprocessor (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/textlayoutmanager (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/uimanager - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-Fabric/uimanager (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - React-FabricImage (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired (= 0.74.2) - - RCTTypeSafety (= 0.74.2) - - React-Fabric - - React-graphics - - React-ImageManager - - React-jsi - - React-jsiexecutor (= 0.74.2) - - React-logger - - React-rendererdebug - - React-utils - - ReactCommon - - Yoga - - React-featureflags (0.74.2) - - React-graphics (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - RCT-Folly/Fabric (= 2024.01.01.00) - - React-Core/Default (= 0.74.2) - - React-utils - - React-hermes (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-cxxreact (= 0.74.2) - - React-jsi - - React-jsiexecutor (= 0.74.2) - - React-jsinspector - - React-perflogger (= 0.74.2) - - React-runtimeexecutor - - React-ImageManager (0.74.2): - - glog - - RCT-Folly/Fabric - - React-Core/Default - - React-debug - - React-Fabric - - React-graphics - - React-rendererdebug - - React-utils - - React-jserrorhandler (0.74.2): - - RCT-Folly/Fabric (= 2024.01.01.00) - - React-debug - - React-jsi - - React-Mapbuffer - - React-jsi (0.74.2): - - boost (= 1.83.0) - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-jsiexecutor (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-cxxreact (= 0.74.2) - - React-jsi (= 0.74.2) - - React-jsinspector - - React-perflogger (= 0.74.2) - - React-jsinspector (0.74.2): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-featureflags - - React-jsi - - React-runtimeexecutor (= 0.74.2) - - React-jsitracing (0.74.2): - - React-jsi - - React-logger (0.74.2): - - glog - - React-Mapbuffer (0.74.2): - - glog - - React-debug - - react-native-ble-plx (3.2.0): - - DoubleConversion - - glog - - hermes-engine - - MultiplatformBleAdapter (= 0.2.0) - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - react-native-safe-area-context (4.10.5): - - React-Core - - React-nativeconfig (0.74.2) - - React-NativeModulesApple (0.74.2): - - glog - - hermes-engine - - React-callinvoker - - React-Core - - React-cxxreact - - React-jsi - - React-jsinspector - - React-runtimeexecutor - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - React-perflogger (0.74.2) - - React-RCTActionSheet (0.74.2): - - React-Core/RCTActionSheetHeaders (= 0.74.2) - - React-RCTAnimation (0.74.2): - - RCT-Folly (= 2024.01.01.00) - - RCTTypeSafety - - React-Codegen - - React-Core/RCTAnimationHeaders - - React-jsi - - React-NativeModulesApple - - ReactCommon - - React-RCTAppDelegate (0.74.2): - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-CoreModules - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-hermes - - React-nativeconfig - - React-NativeModulesApple - - React-RCTFabric - - React-RCTImage - - React-RCTNetwork - - React-rendererdebug - - React-RuntimeApple - - React-RuntimeCore - - React-RuntimeHermes - - React-runtimescheduler - - React-utils - - ReactCommon - - React-RCTBlob (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-Codegen - - React-Core/RCTBlobHeaders - - React-Core/RCTWebSocket - - React-jsi - - React-jsinspector - - React-NativeModulesApple - - React-RCTNetwork - - ReactCommon - - React-RCTFabric (0.74.2): - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - React-Core - - React-debug - - React-Fabric - - React-FabricImage - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-jsinspector - - React-nativeconfig - - React-RCTImage - - React-RCTText - - React-rendererdebug - - React-runtimescheduler - - React-utils - - Yoga - - React-RCTImage (0.74.2): - - RCT-Folly (= 2024.01.01.00) - - RCTTypeSafety - - React-Codegen - - React-Core/RCTImageHeaders - - React-jsi - - React-NativeModulesApple - - React-RCTNetwork - - ReactCommon - - React-RCTLinking (0.74.2): - - React-Codegen - - React-Core/RCTLinkingHeaders (= 0.74.2) - - React-jsi (= 0.74.2) - - React-NativeModulesApple - - ReactCommon - - ReactCommon/turbomodule/core (= 0.74.2) - - React-RCTNetwork (0.74.2): - - RCT-Folly (= 2024.01.01.00) - - RCTTypeSafety - - React-Codegen - - React-Core/RCTNetworkHeaders - - React-jsi - - React-NativeModulesApple - - ReactCommon - - React-RCTSettings (0.74.2): - - RCT-Folly (= 2024.01.01.00) - - RCTTypeSafety - - React-Codegen - - React-Core/RCTSettingsHeaders - - React-jsi - - React-NativeModulesApple - - ReactCommon - - React-RCTText (0.74.2): - - React-Core/RCTTextHeaders (= 0.74.2) - - Yoga - - React-RCTVibration (0.74.2): - - RCT-Folly (= 2024.01.01.00) - - React-Codegen - - React-Core/RCTVibrationHeaders - - React-jsi - - React-NativeModulesApple - - ReactCommon - - React-rendererdebug (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - RCT-Folly (= 2024.01.01.00) - - React-debug - - React-rncore (0.74.2) - - React-RuntimeApple (0.74.2): - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - React-callinvoker - - React-Core/Default - - React-CoreModules - - React-cxxreact - - React-jserrorhandler - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-Mapbuffer - - React-NativeModulesApple - - React-RCTFabric - - React-RuntimeCore - - React-runtimeexecutor - - React-RuntimeHermes - - React-utils - - React-RuntimeCore (0.74.2): - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - React-cxxreact - - React-featureflags - - React-jserrorhandler - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - React-runtimeexecutor (0.74.2): - - React-jsi (= 0.74.2) - - React-RuntimeHermes (0.74.2): - - hermes-engine - - RCT-Folly/Fabric (= 2024.01.01.00) - - React-featureflags - - React-hermes - - React-jsi - - React-jsinspector - - React-jsitracing - - React-nativeconfig - - React-RuntimeCore - - React-utils - - React-runtimescheduler (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-callinvoker - - React-cxxreact - - React-debug - - React-featureflags - - React-jsi - - React-rendererdebug - - React-runtimeexecutor - - React-utils - - React-utils (0.74.2): - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-debug - - React-jsi (= 0.74.2) - - ReactCommon (0.74.2): - - ReactCommon/turbomodule (= 0.74.2) - - ReactCommon/turbomodule (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.74.2) - - React-cxxreact (= 0.74.2) - - React-jsi (= 0.74.2) - - React-logger (= 0.74.2) - - React-perflogger (= 0.74.2) - - ReactCommon/turbomodule/bridging (= 0.74.2) - - ReactCommon/turbomodule/core (= 0.74.2) - - ReactCommon/turbomodule/bridging (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.74.2) - - React-cxxreact (= 0.74.2) - - React-jsi (= 0.74.2) - - React-logger (= 0.74.2) - - React-perflogger (= 0.74.2) - - ReactCommon/turbomodule/core (0.74.2): - - DoubleConversion - - fmt (= 9.1.0) - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.74.2) - - React-cxxreact (= 0.74.2) - - React-debug (= 0.74.2) - - React-jsi (= 0.74.2) - - React-logger (= 0.74.2) - - React-perflogger (= 0.74.2) - - React-utils (= 0.74.2) - - RNScreens (3.32.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-RCTImage - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - SocketRocket (0.7.0) - - Yoga (0.0.0) - -DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - EXConstants (from `../node_modules/expo-constants/ios`) - - Expo (from `../node_modules/expo`) - - ExpoAsset (from `../node_modules/expo-asset/ios`) - - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - - ExpoFont (from `../node_modules/expo-font/ios`) - - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - - ExpoModulesCore (from `../node_modules/expo-modules-core`) - - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../node_modules/react-native/`) - - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - - React-Codegen (from `build/generated/ios`) - - React-Core (from `../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) - - React-Fabric (from `../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) - - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - - react-native-ble-plx (from `../node_modules/react-native-ble-plx`) - - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../node_modules/react-native/React`) - - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-rncore (from `../node_modules/react-native/ReactCommon`) - - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - RNScreens (from `../node_modules/react-native-screens`) - - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) - -SPEC REPOS: - trunk: - - MultiplatformBleAdapter - - SocketRocket - -EXTERNAL SOURCES: - boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" - DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" - EXConstants: - :path: "../node_modules/expo-constants/ios" - Expo: - :path: "../node_modules/expo" - ExpoAsset: - :path: "../node_modules/expo-asset/ios" - ExpoFileSystem: - :path: "../node_modules/expo-file-system/ios" - ExpoFont: - :path: "../node_modules/expo-font/ios" - ExpoKeepAwake: - :path: "../node_modules/expo-keep-awake/ios" - ExpoModulesCore: - :path: "../node_modules/expo-modules-core" - FBLazyVector: - :path: "../node_modules/react-native/Libraries/FBLazyVector" - fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" - glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" - hermes-engine: - :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-2024-06-03-RNv0.74.2-bb1e74fe1e95c2b5a2f4f9311152da052badc2bc - RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" - RCTDeprecation: - :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" - RCTRequired: - :path: "../node_modules/react-native/Libraries/Required" - RCTTypeSafety: - :path: "../node_modules/react-native/Libraries/TypeSafety" - React: - :path: "../node_modules/react-native/" - React-callinvoker: - :path: "../node_modules/react-native/ReactCommon/callinvoker" - React-Codegen: - :path: build/generated/ios - React-Core: - :path: "../node_modules/react-native/" - React-CoreModules: - :path: "../node_modules/react-native/React/CoreModules" - React-cxxreact: - :path: "../node_modules/react-native/ReactCommon/cxxreact" - React-debug: - :path: "../node_modules/react-native/ReactCommon/react/debug" - React-Fabric: - :path: "../node_modules/react-native/ReactCommon" - React-FabricImage: - :path: "../node_modules/react-native/ReactCommon" - React-featureflags: - :path: "../node_modules/react-native/ReactCommon/react/featureflags" - React-graphics: - :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" - React-hermes: - :path: "../node_modules/react-native/ReactCommon/hermes" - React-ImageManager: - :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" - React-jserrorhandler: - :path: "../node_modules/react-native/ReactCommon/jserrorhandler" - React-jsi: - :path: "../node_modules/react-native/ReactCommon/jsi" - React-jsiexecutor: - :path: "../node_modules/react-native/ReactCommon/jsiexecutor" - React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" - React-jsitracing: - :path: "../node_modules/react-native/ReactCommon/hermes/executor/" - React-logger: - :path: "../node_modules/react-native/ReactCommon/logger" - React-Mapbuffer: - :path: "../node_modules/react-native/ReactCommon" - react-native-ble-plx: - :path: "../node_modules/react-native-ble-plx" - react-native-safe-area-context: - :path: "../node_modules/react-native-safe-area-context" - React-nativeconfig: - :path: "../node_modules/react-native/ReactCommon" - React-NativeModulesApple: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" - React-perflogger: - :path: "../node_modules/react-native/ReactCommon/reactperflogger" - React-RCTActionSheet: - :path: "../node_modules/react-native/Libraries/ActionSheetIOS" - React-RCTAnimation: - :path: "../node_modules/react-native/Libraries/NativeAnimation" - React-RCTAppDelegate: - :path: "../node_modules/react-native/Libraries/AppDelegate" - React-RCTBlob: - :path: "../node_modules/react-native/Libraries/Blob" - React-RCTFabric: - :path: "../node_modules/react-native/React" - React-RCTImage: - :path: "../node_modules/react-native/Libraries/Image" - React-RCTLinking: - :path: "../node_modules/react-native/Libraries/LinkingIOS" - React-RCTNetwork: - :path: "../node_modules/react-native/Libraries/Network" - React-RCTSettings: - :path: "../node_modules/react-native/Libraries/Settings" - React-RCTText: - :path: "../node_modules/react-native/Libraries/Text" - React-RCTVibration: - :path: "../node_modules/react-native/Libraries/Vibration" - React-rendererdebug: - :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" - React-rncore: - :path: "../node_modules/react-native/ReactCommon" - React-RuntimeApple: - :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" - React-RuntimeCore: - :path: "../node_modules/react-native/ReactCommon/react/runtime" - React-runtimeexecutor: - :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" - React-RuntimeHermes: - :path: "../node_modules/react-native/ReactCommon/react/runtime" - React-runtimescheduler: - :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" - React-utils: - :path: "../node_modules/react-native/ReactCommon/react/utils" - ReactCommon: - :path: "../node_modules/react-native/ReactCommon" - RNScreens: - :path: "../node_modules/react-native-screens" - Yoga: - :path: "../node_modules/react-native/ReactCommon/yoga" - -SPEC CHECKSUMS: - boost: d3f49c53809116a5d38da093a8aa78bf551aed09 - DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 - EXConstants: 409690fbfd5afea964e5e9d6c4eb2c2b59222c59 - Expo: 9c87c876b45a6934894ba5e9353ee94fdba48125 - ExpoAsset: 39e60dc31b16e204a9949caf3edb6dcda322e667 - ExpoFileSystem: 80bfe850b1f9922c16905822ecbf97acd711dc51 - ExpoFont: 43b69559cef3d773db57c7ae7edd3cb0aa0dc610 - ExpoKeepAwake: 3b8815d9dd1d419ee474df004021c69fdd316d08 - ExpoModulesCore: 54c85b57000a2a82e5212d5e42cd7a36524be4ac - FBLazyVector: 4bc164e5b5e6cfc288d2b5ff28643ea15fa1a589 - fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 - glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f - hermes-engine: 01d3e052018c2a13937aca1860fbedbccd4a41b7 - MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d - RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 - RCTDeprecation: b03c35057846b685b3ccadc9bfe43e349989cdb2 - RCTRequired: 194626909cfa8d39ca6663138c417bc6c431648c - RCTTypeSafety: 552aff5b8e8341660594db00e53ac889682bc120 - React: a57fe42044fe6ed3e828f8867ce070a6c5872754 - React-callinvoker: 6bedefb354a8848b534752417954caa3a5cf34f9 - React-Codegen: 0952549a095f8f8cb2fb5def1733b6b232769b1c - React-Core: 289ee3dfc1639bb9058c1e77427bb48169c26d7a - React-CoreModules: eda5ce541a1f552158317abd90a5a0f6a4f8d6f7 - React-cxxreact: 56bd17ccc6d4248116f7f95884ddb8c528379fb6 - React-debug: 164b8e302404d92d4bec39778a5e03bcb1b6eb08 - React-Fabric: 05620c36074e3ab397dd8f9db0deb6d3c38b4efa - React-FabricImage: 2a8a7f5729f5c44e32e6f58f7225ee1017ed0704 - React-featureflags: d97a6393993052e951e16a3b81206e22110be8d2 - React-graphics: ef07d701f4eb72ae6fca6ed0a7260a04f2a58dec - React-hermes: 6ccc301ababfa17a9aad25a7e33faf325fd024b4 - React-ImageManager: 00404bfe122626bc6493621f2a31ce802115a9b3 - React-jserrorhandler: 5e2632590a84363855b2083e6b3d501e93bc3f04 - React-jsi: 828703c235f4eea1647897ee8030efdc6e8e9f14 - React-jsiexecutor: 713d7bbef0a410cee5b3b78f73ed1fc16e177ba7 - React-jsinspector: e1fa5325a47f34645195c63e3312ddb6a2efef5d - React-jsitracing: 0fa7f78d8fdda794667cb2e6f19c874c1cf31d7e - React-logger: 29fa3e048f5f67fe396bc08af7606426d9bd7b5d - React-Mapbuffer: bf56147c9775491e53122a94c423ac201417e326 - react-native-ble-plx: 892f782670cff6cfdd1df25a572457b033e94437 - react-native-safe-area-context: a240ad4b683349e48b1d51fed1611138d1bdad97 - React-nativeconfig: 9f223cd321823afdecf59ed00861ab2d69ee0fc1 - React-NativeModulesApple: ff7efaff7098639db5631236cfd91d60abff04c0 - React-perflogger: 32ed45d9cee02cf6639acae34251590dccd30994 - React-RCTActionSheet: 19f967ddaea258182b56ef11437133b056ba2adf - React-RCTAnimation: d7f4137fc44a08bba465267ea7cb1dbdb7c4ec87 - React-RCTAppDelegate: 2b3f4d8009796af209a0d496e73276b743acee08 - React-RCTBlob: c6c3e1e0251700b7bea036b893913f22e2b9cb47 - React-RCTFabric: 93a3ea55169d19294f07092013c1c9ea7a015c9b - React-RCTImage: 40528ab74a4fef0f0e2ee797a074b26d120b6cc6 - React-RCTLinking: 385b5beb96749aae9ae1606746e883e1c9f8a6a7 - React-RCTNetwork: ffc9f05bd8fa5b3bce562199ba41235ad0af645c - React-RCTSettings: 21914178bb65cb2c20c655ae1fb401617ae74618 - React-RCTText: 7f8dba1a311e99f4de15bbace2350e805f33f024 - React-RCTVibration: e4ccf673579d0d94a96b3a0b64492db08f8324d5 - React-rendererdebug: ac70f40de137ce7bdbc55eaee60c467a215d9923 - React-rncore: edfff7a3f7f82ca1e0ba26978c6d84c7a8970dac - React-RuntimeApple: a0c98b75571aa5f44ddc7c6e9fd55803fa4db00f - React-RuntimeCore: 4b8db1fe2f3f4a3a5ecb22e1a419824e3e2cd7ef - React-runtimeexecutor: 5961acc7a77b69f964e1645a5d6069e124ce6b37 - React-RuntimeHermes: c5825bfae4815fdf4e9e639340c3a986a491884c - React-runtimescheduler: 56b642bf605ba5afa500d35790928fc1d51565ad - React-utils: 4476b7fcbbd95cfd002f3e778616155241d86e31 - ReactCommon: ecad995f26e0d1e24061f60f4e5d74782f003f12 - RNScreens: 5aeecbb09aa7285379b6e9f3c8a3c859bb16401c - SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - Yoga: 2f71ecf38d934aecb366e686278102a51679c308 - -PODFILE CHECKSUM: b672dc7575712add354d293bfc3a2865be0943ef - -COCOAPODS: 1.14.3 diff --git a/example-expo/ios/Podfile.properties.json b/example-expo/ios/Podfile.properties.json deleted file mode 100644 index de9f7b752..000000000 --- a/example-expo/ios/Podfile.properties.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "expo.jsEngine": "hermes", - "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true" -} diff --git a/example-expo/ios/exampleexpo.xcodeproj/project.pbxproj b/example-expo/ios/exampleexpo.xcodeproj/project.pbxproj deleted file mode 100644 index 50e479679..000000000 --- a/example-expo/ios/exampleexpo.xcodeproj/project.pbxproj +++ /dev/null @@ -1,537 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; - 3E8495B6C9D6144C1400C08F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 26FA5883C388FB56B863B4DF /* PrivacyInfo.xcprivacy */; }; - 51446F710D5045AA93FD119F /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B49FFF45F2450AAB6FE804 /* noop-file.swift */; }; - 96905EF65AED1B983A6B3ABC /* libPods-exampleexpo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-exampleexpo.a */; }; - B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; }; - BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 121749BFA28542E0AE175DE7 /* exampleexpo-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "exampleexpo-Bridging-Header.h"; path = "exampleexpo/exampleexpo-Bridging-Header.h"; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* exampleexpo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = exampleexpo.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = exampleexpo/AppDelegate.h; sourceTree = ""; }; - 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = exampleexpo/AppDelegate.mm; sourceTree = ""; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = exampleexpo/Images.xcassets; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = exampleexpo/Info.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = exampleexpo/main.m; sourceTree = ""; }; - 26FA5883C388FB56B863B4DF /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = exampleexpo/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-exampleexpo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-exampleexpo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 6C2E3173556A471DD304B334 /* Pods-exampleexpo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-exampleexpo.debug.xcconfig"; path = "Target Support Files/Pods-exampleexpo/Pods-exampleexpo.debug.xcconfig"; sourceTree = ""; }; - 7A4D352CD337FB3A3BF06240 /* Pods-exampleexpo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-exampleexpo.release.xcconfig"; path = "Target Support Files/Pods-exampleexpo/Pods-exampleexpo.release.xcconfig"; sourceTree = ""; }; - A9B49FFF45F2450AAB6FE804 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "exampleexpo/noop-file.swift"; sourceTree = ""; }; - AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = exampleexpo/SplashScreen.storyboard; sourceTree = ""; }; - BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; - ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-exampleexpo/ExpoModulesProvider.swift"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 96905EF65AED1B983A6B3ABC /* libPods-exampleexpo.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 13B07FAE1A68108700A75B9A /* exampleexpo */ = { - isa = PBXGroup; - children = ( - BB2F792B24A3F905000567C9 /* Supporting */, - 13B07FAF1A68108700A75B9A /* AppDelegate.h */, - 13B07FB01A68108700A75B9A /* AppDelegate.mm */, - 13B07FB51A68108700A75B9A /* Images.xcassets */, - 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB71A68108700A75B9A /* main.m */, - AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, - A9B49FFF45F2450AAB6FE804 /* noop-file.swift */, - 121749BFA28542E0AE175DE7 /* exampleexpo-Bridging-Header.h */, - 26FA5883C388FB56B863B4DF /* PrivacyInfo.xcprivacy */, - ); - name = exampleexpo; - sourceTree = ""; - }; - 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { - isa = PBXGroup; - children = ( - ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-exampleexpo.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 832341AE1AAA6A7D00B99B32 /* Libraries */ = { - isa = PBXGroup; - children = ( - ); - name = Libraries; - sourceTree = ""; - }; - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( - 13B07FAE1A68108700A75B9A /* exampleexpo */, - 832341AE1AAA6A7D00B99B32 /* Libraries */, - 83CBBA001A601CBA00E9B192 /* Products */, - 2D16E6871FA4F8E400B85C8A /* Frameworks */, - D65327D7A22EEC0BE12398D9 /* Pods */, - D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 83CBBA001A601CBA00E9B192 /* Products */ = { - isa = PBXGroup; - children = ( - 13B07F961A680F5B00A75B9A /* exampleexpo.app */, - ); - name = Products; - sourceTree = ""; - }; - 92DBD88DE9BF7D494EA9DA96 /* exampleexpo */ = { - isa = PBXGroup; - children = ( - FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */, - ); - name = exampleexpo; - sourceTree = ""; - }; - BB2F792B24A3F905000567C9 /* Supporting */ = { - isa = PBXGroup; - children = ( - BB2F792C24A3F905000567C9 /* Expo.plist */, - ); - name = Supporting; - path = exampleexpo/Supporting; - sourceTree = ""; - }; - D65327D7A22EEC0BE12398D9 /* Pods */ = { - isa = PBXGroup; - children = ( - 6C2E3173556A471DD304B334 /* Pods-exampleexpo.debug.xcconfig */, - 7A4D352CD337FB3A3BF06240 /* Pods-exampleexpo.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = { - isa = PBXGroup; - children = ( - 92DBD88DE9BF7D494EA9DA96 /* exampleexpo */, - ); - name = ExpoModulesProviders; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 13B07F861A680F5B00A75B9A /* exampleexpo */ = { - isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "exampleexpo" */; - buildPhases = ( - 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, - 4DEDFC3EDBA564850AC05B3A /* [Expo] Configure project */, - 13B07F871A680F5B00A75B9A /* Sources */, - 13B07F8C1A680F5B00A75B9A /* Frameworks */, - 13B07F8E1A680F5B00A75B9A /* Resources */, - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, - 16E6D6C10C6187366A605ECC /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = exampleexpo; - productName = exampleexpo; - productReference = 13B07F961A680F5B00A75B9A /* exampleexpo.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1130; - TargetAttributes = { - 13B07F861A680F5B00A75B9A = { - LastSwiftMigration = 1250; - }; - }; - }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "exampleexpo" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 13B07F861A680F5B00A75B9A /* exampleexpo */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 13B07F8E1A680F5B00A75B9A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, - 3E8495B6C9D6144C1400C08F /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Bundle React Native code and images"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; - }; - 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-exampleexpo-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 16E6D6C10C6187366A605ECC /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-exampleexpo/Pods-exampleexpo-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-exampleexpo/Pods-exampleexpo-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 4DEDFC3EDBA564850AC05B3A /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-exampleexpo/expo-configure-project.sh\"\n"; - }; - 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-exampleexpo/Pods-exampleexpo-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-exampleexpo/Pods-exampleexpo-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 13B07F871A680F5B00A75B9A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, - B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */, - 51446F710D5045AA93FD119F /* noop-file.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 13B07F941A680F5B00A75B9A /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-exampleexpo.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = exampleexpo/exampleexpo.entitlements; - CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "FB_SONARKIT_ENABLED=1", - ); - INFOPLIST_FILE = exampleexpo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; - PRODUCT_BUNDLE_IDENTIFIER = com.withintent.bleplxexample; - PRODUCT_NAME = exampleexpo; - SWIFT_OBJC_BRIDGING_HEADER = "exampleexpo/exampleexpo-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 13B07F951A680F5B00A75B9A /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-exampleexpo.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = exampleexpo/exampleexpo.entitlements; - CURRENT_PROJECT_VERSION = 1; - INFOPLIST_FILE = exampleexpo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; - PRODUCT_BUNDLE_IDENTIFIER = com.withintent.bleplxexample; - PRODUCT_NAME = exampleexpo; - SWIFT_OBJC_BRIDGING_HEADER = "exampleexpo/exampleexpo-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CC = ""; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++20"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CXX = ""; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD = ""; - LDPLUSPLUS = ""; - LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; - LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; - SDKROOT = iphoneos; - USE_HERMES = true; - }; - name = Debug; - }; - 83CBBA211A601CBA00E9B192 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CC = ""; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++20"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - CXX = ""; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD = ""; - LDPLUSPLUS = ""; - LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; - LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; - SDKROOT = iphoneos; - USE_HERMES = true; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "exampleexpo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "exampleexpo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} diff --git a/example-expo/ios/exampleexpo.xcodeproj/xcshareddata/xcschemes/exampleexpo.xcscheme b/example-expo/ios/exampleexpo.xcodeproj/xcshareddata/xcschemes/exampleexpo.xcscheme deleted file mode 100644 index 68699179f..000000000 --- a/example-expo/ios/exampleexpo.xcodeproj/xcshareddata/xcschemes/exampleexpo.xcscheme +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example-expo/ios/exampleexpo.xcworkspace/contents.xcworkspacedata b/example-expo/ios/exampleexpo.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d7f47d82e..000000000 --- a/example-expo/ios/exampleexpo.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/example-expo/ios/exampleexpo/AppDelegate.h b/example-expo/ios/exampleexpo/AppDelegate.h deleted file mode 100644 index 1658a437e..000000000 --- a/example-expo/ios/exampleexpo/AppDelegate.h +++ /dev/null @@ -1,7 +0,0 @@ -#import -#import -#import - -@interface AppDelegate : EXAppDelegateWrapper - -@end diff --git a/example-expo/ios/exampleexpo/AppDelegate.mm b/example-expo/ios/exampleexpo/AppDelegate.mm deleted file mode 100644 index b27f83286..000000000 --- a/example-expo/ios/exampleexpo/AppDelegate.mm +++ /dev/null @@ -1,62 +0,0 @@ -#import "AppDelegate.h" - -#import -#import - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - self.moduleName = @"main"; - - // You can add your custom initial props in the dictionary below. - // They will be passed down to the ViewController used by React Native. - self.initialProps = @{}; - - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge -{ - return [self bundleURL]; -} - -- (NSURL *)bundleURL -{ -#if DEBUG - return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; -#else - return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#endif -} - -// Linking API -- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { - return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; -} - -// Universal Links -- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { - BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; - return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; -} - -// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries -- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken -{ - return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; -} - -// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries -- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error -{ - return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; -} - -// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler -{ - return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; -} - -@end diff --git a/example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png deleted file mode 100644 index 2732229fa..000000000 Binary files a/example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png and /dev/null differ diff --git a/example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/Contents.json b/example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 90d8d4c2a..000000000 --- a/example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "images": [ - { - "filename": "App-Icon-1024x1024@1x.png", - "idiom": "universal", - "platform": "ios", - "size": "1024x1024" - } - ], - "info": { - "version": 1, - "author": "expo" - } -} \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/Images.xcassets/Contents.json b/example-expo/ios/exampleexpo/Images.xcassets/Contents.json deleted file mode 100644 index ed285c2e5..000000000 --- a/example-expo/ios/exampleexpo/Images.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "expo" - } -} diff --git a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/Contents.json b/example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/Contents.json deleted file mode 100644 index 3cf848977..000000000 --- a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "filename": "image.png", - "scale": "1x" - }, - { - "idiom": "universal", - "scale": "2x" - }, - { - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "version": 1, - "author": "expo" - } -} \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/image.png b/example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/image.png deleted file mode 100644 index c52c2c680..000000000 Binary files a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/image.png and /dev/null differ diff --git a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/Contents.json b/example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/Contents.json deleted file mode 100644 index 3cf848977..000000000 --- a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images": [ - { - "idiom": "universal", - "filename": "image.png", - "scale": "1x" - }, - { - "idiom": "universal", - "scale": "2x" - }, - { - "idiom": "universal", - "scale": "3x" - } - ], - "info": { - "version": 1, - "author": "expo" - } -} \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/image.png b/example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/image.png deleted file mode 100644 index 33ddf20bb..000000000 Binary files a/example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/image.png and /dev/null differ diff --git a/example-expo/ios/exampleexpo/Info.plist b/example-expo/ios/exampleexpo/Info.plist deleted file mode 100644 index a57111dd8..000000000 --- a/example-expo/ios/exampleexpo/Info.plist +++ /dev/null @@ -1,72 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - example-expo - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLSchemes - - com.withintent.bleplxexample - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - - UILaunchStoryboardName - SplashScreen - UIRequiredDeviceCapabilities - - arm64 - - UIRequiresFullScreen - - UIStatusBarStyle - UIStatusBarStyleDefault - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIUserInterfaceStyle - Light - UIViewControllerBasedStatusBarAppearance - - - \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/PrivacyInfo.xcprivacy b/example-expo/ios/exampleexpo/PrivacyInfo.xcprivacy deleted file mode 100644 index 5bb83c5d4..000000000 --- a/example-expo/ios/exampleexpo/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,48 +0,0 @@ - - - - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - NSPrivacyAccessedAPITypeReasons - - CA92.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp - NSPrivacyAccessedAPITypeReasons - - 0A2A.1 - 3B52.1 - C617.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryDiskSpace - NSPrivacyAccessedAPITypeReasons - - E174.1 - 85F4.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategorySystemBootTime - NSPrivacyAccessedAPITypeReasons - - 35F9.1 - - - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/example-expo/ios/exampleexpo/SplashScreen.storyboard b/example-expo/ios/exampleexpo/SplashScreen.storyboard deleted file mode 100644 index ed03a5299..000000000 --- a/example-expo/ios/exampleexpo/SplashScreen.storyboard +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/Supporting/Expo.plist b/example-expo/ios/exampleexpo/Supporting/Expo.plist deleted file mode 100644 index 750be020c..000000000 --- a/example-expo/ios/exampleexpo/Supporting/Expo.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - EXUpdatesCheckOnLaunch - ALWAYS - EXUpdatesEnabled - - EXUpdatesLaunchWaitMs - 0 - - \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/exampleexpo-Bridging-Header.h b/example-expo/ios/exampleexpo/exampleexpo-Bridging-Header.h deleted file mode 100644 index e11d920b1..000000000 --- a/example-expo/ios/exampleexpo/exampleexpo-Bridging-Header.h +++ /dev/null @@ -1,3 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// diff --git a/example-expo/ios/exampleexpo/exampleexpo.entitlements b/example-expo/ios/exampleexpo/exampleexpo.entitlements deleted file mode 100644 index f683276c5..000000000 --- a/example-expo/ios/exampleexpo/exampleexpo.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example-expo/ios/exampleexpo/main.m b/example-expo/ios/exampleexpo/main.m deleted file mode 100644 index 25181b6cc..000000000 --- a/example-expo/ios/exampleexpo/main.m +++ /dev/null @@ -1,10 +0,0 @@ -#import - -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} - diff --git a/example-expo/ios/exampleexpo/noop-file.swift b/example-expo/ios/exampleexpo/noop-file.swift deleted file mode 100644 index b2ffafbfc..000000000 --- a/example-expo/ios/exampleexpo/noop-file.swift +++ /dev/null @@ -1,4 +0,0 @@ -// -// @generated -// A blank Swift file must be created for native modules with Swift files to work correctly. -// diff --git a/example-expo/metro.config.js b/example-expo/metro.config.js new file mode 100644 index 000000000..6c38f6908 --- /dev/null +++ b/example-expo/metro.config.js @@ -0,0 +1,34 @@ +const { getDefaultConfig } = require('expo/metro-config'); +const path = require('path'); + +const projectRoot = __dirname; +const libraryRoot = path.resolve(__dirname, '..'); +const exampleNodeModules = path.resolve(projectRoot, 'node_modules'); + +const forcedModules = ['react', 'react-native']; + +const config = getDefaultConfig(projectRoot); + +// Watch the parent library source for live editing +config.watchFolders = [libraryRoot]; + +// Only resolve from example-expo's node_modules — NOT the library root +config.resolver.nodeModulesPaths = [exampleNodeModules]; + +// Force react and react-native to resolve from this app's node_modules +const originalResolveRequest = config.resolver.resolveRequest; +config.resolver.resolveRequest = (context, moduleName, platform) => { + if (forcedModules.includes(moduleName)) { + return context.resolveRequest( + { ...context, originModulePath: path.join(exampleNodeModules, 'react-native', 'dummy.js') }, + moduleName, + platform, + ); + } + if (originalResolveRequest) { + return originalResolveRequest(context, moduleName, platform); + } + return context.resolveRequest(context, moduleName, platform); +}; + +module.exports = config; diff --git a/example-expo/package.json b/example-expo/package.json index d101d6fa9..4daa8f19a 100644 --- a/example-expo/package.json +++ b/example-expo/package.json @@ -1,40 +1,47 @@ { "name": "example-expo", + "main": "expo-router/entry", "version": "1.0.0", - "main": "expo/AppEntry.js", "scripts": { "start": "expo start", + "reset-project": "node ./scripts/reset-project.js", "android": "expo run:android", "ios": "expo run:ios", - "web": "expo start --web" + "web": "expo start --web", + "lint": "expo lint" }, "dependencies": { - "expo": "~51.0.14", - "expo-status-bar": "~1.12.1", - "react": "18.2.0", - "react-native": "0.74.2", - "@react-navigation/native": "^6.1.17", - "@react-navigation/native-stack": "^6.9.26", - "react-native-base64": "^0.2.1", - "react-native-safe-area-context": "^4.10.1", - "react-native-screens": "^3.31.1", - "react-native-toast-message": "^2.1.6", - "styled-components": "^6.0.7", - "react-native-ble-plx": "../" + "@react-navigation/bottom-tabs": "^7.7.3", + "@react-navigation/elements": "^2.8.1", + "@react-navigation/native": "^7.1.28", + "expo": "~55.0.6", + "expo-build-properties": "~55.0.9", + "expo-constants": "~55.0.7", + "expo-device": "~55.0.9", + "expo-font": "~55.0.4", + "expo-glass-effect": "~55.0.8", + "expo-image": "~55.0.6", + "expo-linking": "~55.0.7", + "expo-router": "~55.0.5", + "expo-splash-screen": "~55.0.10", + "expo-status-bar": "~55.0.4", + "expo-symbols": "~55.0.5", + "expo-system-ui": "~55.0.9", + "expo-web-browser": "~55.0.9", + "react": "19.2.0", + "react-dom": "19.2.0", + "react-native": "0.83.2", + "react-native-gesture-handler": "~2.30.0", + "react-native-worklets": "0.7.2", + "react-native-reanimated": "4.2.1", + "react-native-safe-area-context": "~5.6.2", + "react-native-screens": "~4.23.0", + "react-native-ble-plx": "file:../", + "react-native-web": "~0.21.0" }, "devDependencies": { - "@babel/core": "^7.20.0", - "@types/react": "~18.2.45", - "typescript": "^5.1.3", - "@babel/preset-env": "^7.20.0", - "@babel/runtime": "^7.20.0", - "@react-native/eslint-config": "^0.72.2", - "@types/react-native-base64": "^0.2.0", - "@react-native/metro-config": "0.74.83", - "babel-plugin-module-resolver": "^5.0.0", - "metro-react-native-babel-preset": "0.76.8", - "@react-native/babel-preset": "0.74.83", - "@react-native/typescript-config": "0.74.83" + "@types/react": "~19.2.2", + "typescript": "~5.9.2" }, "private": true } diff --git a/example-expo/scripts/reset-project.js b/example-expo/scripts/reset-project.js new file mode 100755 index 000000000..055d15b1d --- /dev/null +++ b/example-expo/scripts/reset-project.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node + +/** + * This script is used to reset the project to a blank state. + * It deletes or moves the /src and /scripts directories to /example based on user input and creates a new /src/app directory with an index.tsx and _layout.tsx file. + * You can remove the `reset-project` script from package.json and safely delete this file after running it. + */ + +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); + +const root = process.cwd(); +const oldDirs = ["src", "scripts"]; +const exampleDir = "example"; +const newAppDir = "src/app"; +const exampleDirPath = path.join(root, exampleDir); + +const indexContent = `import { Text, View, StyleSheet } from "react-native"; + +export default function Index() { + return ( + + Edit src/app/index.tsx to edit this screen. + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: "center", + justifyContent: "center", + }, +}); +`; + +const layoutContent = `import { Stack } from "expo-router"; + +export default function RootLayout() { + return ; +} +`; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const moveDirectories = async (userInput) => { + try { + if (userInput === "y") { + // Create the app-example directory + await fs.promises.mkdir(exampleDirPath, { recursive: true }); + console.log(`📁 /${exampleDir} directory created.`); + } + + // Move old directories to new app-example directory or delete them + for (const dir of oldDirs) { + const oldDirPath = path.join(root, dir); + if (fs.existsSync(oldDirPath)) { + if (userInput === "y") { + const newDirPath = path.join(root, exampleDir, dir); + await fs.promises.rename(oldDirPath, newDirPath); + console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`); + } else { + await fs.promises.rm(oldDirPath, { recursive: true, force: true }); + console.log(`❌ /${dir} deleted.`); + } + } else { + console.log(`➡️ /${dir} does not exist, skipping.`); + } + } + + // Create new /src/app directory + const newAppDirPath = path.join(root, newAppDir); + await fs.promises.mkdir(newAppDirPath, { recursive: true }); + console.log("\n📁 New /src/app directory created."); + + // Create index.tsx + const indexPath = path.join(newAppDirPath, "index.tsx"); + await fs.promises.writeFile(indexPath, indexContent); + console.log("📄 src/app/index.tsx created."); + + // Create _layout.tsx + const layoutPath = path.join(newAppDirPath, "_layout.tsx"); + await fs.promises.writeFile(layoutPath, layoutContent); + console.log("📄 src/app/_layout.tsx created."); + + console.log("\n✅ Project reset complete. Next steps:"); + console.log( + `1. Run \`npx expo start\` to start a development server.\n2. Edit src/app/index.tsx to edit the main screen.\n3. Put all your application code in /src, only screens and layout files should be in /src/app.${ + userInput === "y" + ? `\n4. Delete the /${exampleDir} directory when you're done referencing it.` + : "" + }` + ); + } catch (error) { + console.error(`❌ Error during script execution: ${error.message}`); + } +}; + +rl.question( + "Do you want to move existing files to /example instead of deleting them? (Y/n): ", + (answer) => { + const userInput = answer.trim().toLowerCase() || "y"; + if (userInput === "y" || userInput === "n") { + moveDirectories(userInput).finally(() => rl.close()); + } else { + console.log("❌ Invalid input. Please enter 'Y' or 'N'."); + rl.close(); + } + } +); diff --git a/example-expo/src/AppComponent.tsx b/example-expo/src/AppComponent.tsx deleted file mode 100644 index 551457eab..000000000 --- a/example-expo/src/AppComponent.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { SafeAreaProvider } from 'react-native-safe-area-context' -import { ThemeProvider } from 'styled-components' -import Toast from 'react-native-toast-message' -import { commonTheme } from './theme/theme' -import { Navigation } from './navigation' - -export function AppComponent() { - return ( - - - - - - - ) -} diff --git a/example-expo/src/components/atoms/AppText/AppText.styled.tsx b/example-expo/src/components/atoms/AppText/AppText.styled.tsx deleted file mode 100644 index 864f4600d..000000000 --- a/example-expo/src/components/atoms/AppText/AppText.styled.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Text } from 'react-native' -import styled, { css } from 'styled-components' - -export const StyledText = styled(Text)` - ${({ theme }) => css` - font-size: ${theme.sizes.defaultFontSize}px; - font-weight: 800; - `} -` diff --git a/example-expo/src/components/atoms/AppText/AppText.tsx b/example-expo/src/components/atoms/AppText/AppText.tsx deleted file mode 100644 index 4d0dc5c38..000000000 --- a/example-expo/src/components/atoms/AppText/AppText.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import type { TextProps } from 'react-native' -import { StyledText } from './AppText.styled' - -export function AppText(props: TextProps) { - return -} diff --git a/example-expo/src/components/atoms/AppTextInput/AppTextInput.styled.tsx b/example-expo/src/components/atoms/AppTextInput/AppTextInput.styled.tsx deleted file mode 100644 index a1b5419ff..000000000 --- a/example-expo/src/components/atoms/AppTextInput/AppTextInput.styled.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { TextInput } from 'react-native' -import styled, { css } from 'styled-components' - -export const StyledTextInput = styled(TextInput)` - ${({ theme }) => css` - font-size: ${theme.sizes.defaultFontSize}px; - font-weight: 800; - border-radius: 100px; - border-color: ${theme.colors.mainRed}; - border-width: 1px; - padding: 0px 24px; - height: 50px; - margin-bottom: 12px; - `} -` diff --git a/example-expo/src/components/atoms/AppTextInput/AppTextInput.tsx b/example-expo/src/components/atoms/AppTextInput/AppTextInput.tsx deleted file mode 100644 index 0e00a1653..000000000 --- a/example-expo/src/components/atoms/AppTextInput/AppTextInput.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import type { TextInputProps } from 'react-native' -import { StyledTextInput } from './AppTextInput.styled' - -export function AppTextInput(props: TextInputProps) { - return -} diff --git a/example-expo/src/components/atoms/Button/Button.styled.tsx b/example-expo/src/components/atoms/Button/Button.styled.tsx deleted file mode 100644 index 414a4eda2..000000000 --- a/example-expo/src/components/atoms/Button/Button.styled.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { TouchableOpacity } from 'react-native' -import styled, { css } from 'styled-components' -import { AppText } from '../AppText/AppText' - -export const Container = styled(TouchableOpacity)` - ${({ theme }) => css` - background-color: ${theme.colors.mainRed}; - margin: 5px 0px; - padding: 12px; - align-items: center; - border-radius: 100px; - `} -` - -export const StyledText = styled(AppText)` - color: white; -` diff --git a/example-expo/src/components/atoms/Button/Button.tsx b/example-expo/src/components/atoms/Button/Button.tsx deleted file mode 100644 index 9a2984bfe..000000000 --- a/example-expo/src/components/atoms/Button/Button.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import type { TouchableOpacityProps } from 'react-native' -import { Container, StyledText } from './Button.styled' - -export type AppButtonProps = TouchableOpacityProps & { - label: string -} - -export function AppButton({ label, ...props }: AppButtonProps) { - return ( - - {label} - - ) -} diff --git a/example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx b/example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx deleted file mode 100644 index c0e6ac126..000000000 --- a/example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SafeAreaView } from 'react-native-safe-area-context' -import styled, { css } from 'styled-components' - -export const Container = styled(SafeAreaView)` - ${({ theme }) => css` - flex: 1; - padding: ${theme.sizes.defaultScreenPadding}px; - `} -` diff --git a/example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx b/example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx deleted file mode 100644 index dd06f9c1d..000000000 --- a/example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { Container } from './ScreenDefaultContainer.styled' - -export type ScreenDefaultContainerProps = { - children: React.ReactNode -} - -export function ScreenDefaultContainer({ children }: ScreenDefaultContainerProps) { - return {children} -} diff --git a/example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx b/example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx deleted file mode 100644 index cc2db6bc8..000000000 --- a/example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { View } from 'react-native' -import styled, { css } from 'styled-components' -import { AppText } from '../AppText/AppText' - -export const Container = styled(View)` - ${({ theme }) => css` - border-bottom-width: 1px; - border-bottom-color: ${theme.colors.mainRed}; - padding-bottom: 5px; - margin: 10px 0px; - `} -` - -export const Header = styled(View)` - flex-direction: row; - justify-content: space-between; -` - -export const Label = styled(AppText)` - flex: 1; - margin-right: 12px; -` diff --git a/example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx b/example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx deleted file mode 100644 index 293665143..000000000 --- a/example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import type { TestStateType } from '../../../types' -import { AppText } from '../AppText/AppText' -import { Container, Header, Label } from './TestStateDisplay.styled' - -export type TestStateDisplayProps = { - label?: string - state?: TestStateType - value?: string -} - -const marks: Record = { - DONE: '\u2705', - ERROR: '\u274C', - WAITING: '\u231B', - IN_PROGRESS: '\u260E' -} - -export function TestStateDisplay({ label, state, value }: TestStateDisplayProps) { - return ( - -
- - {!!state && {marks[state]}} -
- {!!value && {value}} -
- ) -} diff --git a/example-expo/src/components/atoms/index.ts b/example-expo/src/components/atoms/index.ts deleted file mode 100644 index 3f14cec6a..000000000 --- a/example-expo/src/components/atoms/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Button/Button' -export * from './AppText/AppText' -export * from './ScreenDefaultContainer/ScreenDefaultContainer' -export * from './TestStateDisplay/TestStateDisplay' -export * from './AppTextInput/AppTextInput' diff --git a/example-expo/src/components/molecules/BleDevice/BleDevice.styled.tsx b/example-expo/src/components/molecules/BleDevice/BleDevice.styled.tsx deleted file mode 100644 index d650a1557..000000000 --- a/example-expo/src/components/molecules/BleDevice/BleDevice.styled.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { TouchableOpacity } from 'react-native' -import styled, { css } from 'styled-components' - -export const Container = styled(TouchableOpacity)` - ${({ theme }) => css` - border-color: ${theme.colors.mainRed}; - border-width: 1px; - padding: 12px; - border-radius: 12px; - margin-top: 12px; - `} -` diff --git a/example-expo/src/components/molecules/BleDevice/BleDevice.tsx b/example-expo/src/components/molecules/BleDevice/BleDevice.tsx deleted file mode 100644 index a1084771c..000000000 --- a/example-expo/src/components/molecules/BleDevice/BleDevice.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import { Device } from 'react-native-ble-plx' -import { Container } from './BleDevice.styled' -import { DeviceProperty } from './DeviceProperty/DeviceProperty' - -export type BleDeviceProps = { - onPress: (device: Device) => void - device: Device -} - -export function BleDevice({ device, onPress }: BleDeviceProps) { - const isConnectableInfoValueIsUnavailable = typeof device.isConnectable !== 'boolean' - const isConnectableValue = device.isConnectable ? 'true' : 'false' - const parsedIsConnectable = isConnectableInfoValueIsUnavailable ? '-' : isConnectableValue - - return ( - onPress(device)}> - - - - - - - - - - ) -} diff --git a/example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx b/example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx deleted file mode 100644 index c96d169d0..000000000 --- a/example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View } from 'react-native' -import styled from 'styled-components' -import { AppText } from '../../../atoms' - -export const Container = styled(View)` - flex-direction: row; - flex-wrap: wrap; -` - -export const StyledTitleText = styled(AppText)` - font-weight: 800; -` - -export const StyledValueText = styled(AppText)` - font-weight: 500; -` diff --git a/example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx b/example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx deleted file mode 100644 index 867904dc8..000000000 --- a/example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { Container, StyledTitleText, StyledValueText } from './DeviceProperty.styled' - -export type DevicePropertyProps = { - name: string - value?: number | string | null -} - -export function DeviceProperty({ name, value }: DevicePropertyProps) { - return ( - - {name}: - {value || '-'} - - ) -} diff --git a/example-expo/src/components/molecules/index.ts b/example-expo/src/components/molecules/index.ts deleted file mode 100644 index 39d6d9ff1..000000000 --- a/example-expo/src/components/molecules/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BleDevice/BleDevice' diff --git a/example-expo/src/consts/nRFDeviceConsts.ts b/example-expo/src/consts/nRFDeviceConsts.ts deleted file mode 100644 index 66395ca69..000000000 --- a/example-expo/src/consts/nRFDeviceConsts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { fullUUID } from 'react-native-ble-plx' -import base64 from 'react-native-base64' -import { getDateAsBase64 } from '../utils/getDateAsBase64' - -export const deviceTimeService = fullUUID('1847') -export const currentTimeCharacteristic = fullUUID('2A2B') -export const deviceTimeCharacteristic = fullUUID('2B90') -export const currentTimeCharacteristicTimeTriggerDescriptor = fullUUID('290E') - -export const writeWithResponseBase64Time = getDateAsBase64(new Date('2022-08-11T08:17:19Z')) -export const writeWithoutResponseBase64Time = getDateAsBase64(new Date('2023-09-12T10:12:16Z')) -export const monitorExpectedMessage = 'Hi, it works!' -export const currentTimeCharacteristicTimeTriggerDescriptorValue = base64.encode('BLE-PLX') diff --git a/example-expo/src/navigation/components/commonScreenOptions.tsx b/example-expo/src/navigation/components/commonScreenOptions.tsx deleted file mode 100644 index 0c412e25d..000000000 --- a/example-expo/src/navigation/components/commonScreenOptions.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { NativeStackNavigationOptions } from '@react-navigation/native-stack' -import { useTheme } from 'styled-components' - -export const useCommonScreenOptions: () => NativeStackNavigationOptions = () => { - const theme = useTheme() - - return { - headerShadowVisible: false, - headerTitleStyle: { - fontSize: 22 - }, - headerTitleAlign: 'center', - headerBackTitleVisible: false, - orientation: 'portrait', - title: '', - headerTintColor: 'white', - headerStyle: { - backgroundColor: theme.colors.mainRed - } - } -} diff --git a/example-expo/src/navigation/components/index.ts b/example-expo/src/navigation/components/index.ts deleted file mode 100644 index 2df86427d..000000000 --- a/example-expo/src/navigation/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './commonScreenOptions' diff --git a/example-expo/src/navigation/index.ts b/example-expo/src/navigation/index.ts deleted file mode 100644 index 9c8479a49..000000000 --- a/example-expo/src/navigation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './navigation' diff --git a/example-expo/src/navigation/navigation.tsx b/example-expo/src/navigation/navigation.tsx deleted file mode 100644 index 28f689f42..000000000 --- a/example-expo/src/navigation/navigation.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import { NavigationContainer, DefaultTheme } from '@react-navigation/native' - -import { MainStackComponent, type MainStackParamList } from './navigators' - -const mainTheme = { - ...DefaultTheme, - dark: false, - colors: { - ...DefaultTheme.colors, - card: 'white', - background: 'white' - } -} - -export type AllScreenTypes = MainStackParamList - -// eslint-disable-next-line prettier/prettier -declare global { - namespace ReactNavigation { - interface RootParamList extends AllScreenTypes {} - } -} - -export function Navigation() { - return ( - - - - ) -} diff --git a/example-expo/src/navigation/navigators/MainStack.tsx b/example-expo/src/navigation/navigators/MainStack.tsx deleted file mode 100644 index d10986d6b..000000000 --- a/example-expo/src/navigation/navigators/MainStack.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react' -import { createNativeStackNavigator } from '@react-navigation/native-stack' -import * as screenComponents from '../../screens' -import { useCommonScreenOptions } from '../components' - -export type MainStackParamList = { - DASHBOARD_SCREEN: undefined - DEVICE_DETAILS_SCREEN: undefined - DEVICE_NRF_TEST_SCREEN: undefined - DEVICE_CONNECT_DISCONNECT_TEST_SCREEN: undefined - AUTODISCONNECT_SCREEN: undefined - INSTANCE_DESTROY_SCREEN: undefined - DEVICE_ON_DISCONNECT_TEST_SCREEN: undefined -} - -const MainStack = createNativeStackNavigator() - -export function MainStackComponent() { - const commonScreenOptions = useCommonScreenOptions() - - return ( - - - - - - - - - ) -} diff --git a/example-expo/src/navigation/navigators/index.ts b/example-expo/src/navigation/navigators/index.ts deleted file mode 100644 index 472fafd1e..000000000 --- a/example-expo/src/navigation/navigators/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './MainStack' diff --git a/example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx b/example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx deleted file mode 100644 index 375a05507..000000000 --- a/example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FlatList, View } from 'react-native' -import styled from 'styled-components' - -export const DropDown = styled(View)` - z-index: 100; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: #00000066; - align-items: center; - justify-content: center; -` - -export const DevicesList = styled(FlatList)` - flex: 1; -` diff --git a/example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx b/example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx deleted file mode 100644 index 229fe96ca..000000000 --- a/example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { FlatList } from 'react-native' -import { Device } from 'react-native-ble-plx' -import { AppButton, AppText, ScreenDefaultContainer } from '../../../components/atoms' -import type { MainStackParamList } from '../../../navigation/navigators' -import { BLEService } from '../../../services' -import { BleDevice } from '../../../components/molecules' -import { cloneDeep } from '../../../utils/cloneDeep' -import { DropDown } from './DashboardScreen.styled' - -type DashboardScreenProps = NativeStackScreenProps -type DeviceExtendedByUpdateTime = Device & { updateTimestamp: number } - -const MIN_TIME_BEFORE_UPDATE_IN_MILLISECONDS = 5000 - -export function DashboardScreen({ navigation }: DashboardScreenProps) { - const [isConnecting, setIsConnecting] = useState(false) - const [foundDevices, setFoundDevices] = useState([]) - - const addFoundDevice = (device: Device) => - setFoundDevices(prevState => { - if (!isFoundDeviceUpdateNecessary(prevState, device)) { - return prevState - } - // deep clone - const nextState = cloneDeep(prevState) - const extendedDevice: DeviceExtendedByUpdateTime = { - ...device, - updateTimestamp: Date.now() + MIN_TIME_BEFORE_UPDATE_IN_MILLISECONDS - } as DeviceExtendedByUpdateTime - - const indexToReplace = nextState.findIndex(currentDevice => currentDevice.id === device.id) - if (indexToReplace === -1) { - return nextState.concat(extendedDevice) - } - nextState[indexToReplace] = extendedDevice - return nextState - }) - - const isFoundDeviceUpdateNecessary = (currentDevices: DeviceExtendedByUpdateTime[], updatedDevice: Device) => { - const currentDevice = currentDevices.find(({ id }) => updatedDevice.id === id) - if (!currentDevice) { - return true - } - return currentDevice.updateTimestamp < Date.now() - } - - const onConnectSuccess = () => { - navigation.navigate('DEVICE_DETAILS_SCREEN') - setIsConnecting(false) - } - - const onConnectFail = () => { - setIsConnecting(false) - } - - const deviceRender = (device: Device) => ( - { - setIsConnecting(true) - BLEService.connectToDevice(pickedDevice.id).then(onConnectSuccess).catch(onConnectFail) - }} - key={device.id} - device={device} - /> - ) - - return ( - - {isConnecting && ( - - Connecting - - )} - { - setFoundDevices([]) - BLEService.initializeBLE().then(() => BLEService.scanDevices(addFoundDevice, null, true)) - }} - /> - { - setFoundDevices([]) - BLEService.initializeBLE().then(() => BLEService.scanDevices(addFoundDevice, null, false)) - }} - /> - - navigation.navigate('DEVICE_NRF_TEST_SCREEN')} /> - BLEService.isDeviceWithIdConnected('asd')} /> - navigation.navigate('DEVICE_CONNECT_DISCONNECT_TEST_SCREEN')} - /> - navigation.navigate('INSTANCE_DESTROY_SCREEN')} /> - navigation.navigate('DEVICE_ON_DISCONNECT_TEST_SCREEN')} /> - deviceRender(item)} - keyExtractor={device => device.id} - /> - - ) -} diff --git a/example-expo/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx b/example-expo/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx deleted file mode 100644 index d7af5e411..000000000 --- a/example-expo/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React, { useRef, useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { BleError, Characteristic, Device, type Subscription, type DeviceId, BleErrorCode } from 'react-native-ble-plx' -import { ScrollView } from 'react-native' -import base64 from 'react-native-base64' -import Toast from 'react-native-toast-message' -import type { TestStateType } from '../../../types' -import { BLEService } from '../../../services' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { currentTimeCharacteristic, deviceTimeService } from '../../../consts/nRFDeviceConsts' -import { wait } from '../../../utils/wait' - -type DeviceConnectDisconnectTestScreenProps = NativeStackScreenProps< - MainStackParamList, - 'DEVICE_CONNECT_DISCONNECT_TEST_SCREEN' -> -const NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO = 10 - -export function DeviceConnectDisconnectTestScreen(_props: DeviceConnectDisconnectTestScreenProps) { - const [expectedDeviceName, setExpectedDeviceName] = useState('') - const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') - const [deviceId, setDeviceId] = useState('') - const [connectCounter, setConnectCounter] = useState(0) - const [characteristicDiscoverCounter, setCharacteristicDiscoverCounter] = useState(0) - const [connectInDisconnectTestCounter, setConnectInDisconnectTestCounter] = useState(0) - const [disconnectCounter, setDisconnectCounter] = useState(0) - const [monitorMessages, setMonitorMessages] = useState([]) - const monitorSubscriptionRef = useRef(null) - - const addMonitorMessage = (message: string) => setMonitorMessages(prevMessages => [...prevMessages, message]) - - const checkDeviceName = (device: Device) => - device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase() - - const startConnectAndDiscover = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices(connectAndDiscoverOnDeviceFound, [deviceTimeService]) - } - - const startConnectAndDisconnect = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices(connectAndDisconnectOnDeviceFound, [deviceTimeService]) - } - - const startConnectOnly = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - setConnectCounter(prevCount => prevCount + 1) - setTestScanDevicesState('DONE') - setDeviceId(device.id) - } - }, - [deviceTimeService] - ) - } - - const connectAndDiscoverOnDeviceFound = async (device: Device) => { - if (checkDeviceName(device)) { - setTestScanDevicesState('DONE') - setDeviceId(device.id) - try { - for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - setConnectCounter(prevCount => prevCount + 1) - console.info(`discovering in ${device.id}`) - await startDiscoverServices() - setCharacteristicDiscoverCounter(prevCount => prevCount + 1) - } - console.info('Multiple connect success') - } catch (error) { - console.error('Multiple connect error') - } - } - } - const connectAndDisconnectOnDeviceFound = async (device: Device) => { - if (checkDeviceName(device)) { - setTestScanDevicesState('DONE') - setDeviceId(device.id) - try { - for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) { - await startConnectToDevice(device) - console.info(`connecting to ${device.id}`) - setConnectInDisconnectTestCounter(prevCount => prevCount + 1) - await startDisconnect(device) - console.info(`disconnecting from ${device.id}`) - setDisconnectCounter(prevCount => prevCount + 1) - } - console.info('connect/disconnect success') - } catch (error) { - console.error('Connect/disconnect error') - } - } - } - - const discoverCharacteristicsOnly = async () => { - if (!deviceId) { - console.error('Device not ready') - return - } - try { - for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) { - console.info(`discovering in ${deviceId}`) - await startDiscoverServices() - setCharacteristicDiscoverCounter(prevCount => prevCount + 1) - } - console.info('Multiple discovering success') - } catch (error) { - console.error('Multiple discovering error') - } - } - - const startConnectToDevice = (device: Device) => BLEService.connectToDevice(device.id) - - const startDiscoverServices = () => BLEService.discoverAllServicesAndCharacteristicsForDevice() - - const startDisconnect = (device: Device) => BLEService.disconnectDeviceById(device.id) - - const startCharacteristicMonitor = (directDeviceId?: DeviceId) => { - if (!deviceId && !directDeviceId) { - console.error('Device not ready') - return - } - monitorSubscriptionRef.current = BLEService.setupCustomMonitor( - directDeviceId || deviceId, - deviceTimeService, - currentTimeCharacteristic, - characteristicListener - ) - } - - const characteristicListener = (error: BleError | null, characteristic: Characteristic | null) => { - if (error) { - if (error.errorCode === BleErrorCode.ServiceNotFound || error.errorCode === BleErrorCode.ServicesNotDiscovered) { - startDiscoverServices().then(() => startCharacteristicMonitor()) - return - } - console.error(JSON.stringify(error)) - } - if (characteristic) { - if (characteristic.value) { - const message = base64.decode(characteristic.value) - console.info(message) - addMonitorMessage(message) - } - } - } - - const setupOnDeviceDisconnected = (directDeviceId?: DeviceId) => { - if (!deviceId && !directDeviceId) { - console.error('Device not ready') - return - } - BLEService.onDeviceDisconnectedCustom(directDeviceId || deviceId, disconnectedListener) - } - - const disconnectedListener = (error: BleError | null, device: Device | null) => { - if (error) { - console.error('onDeviceDisconnected') - console.error(JSON.stringify(error, null, 4)) - } - if (device) { - console.info(JSON.stringify(device, null, 4)) - } - } - - // https://github.com/dotintent/react-native-ble-plx/issues/1103 - const showIssue1103Crash = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - setConnectCounter(prevCount => prevCount + 1) - setTestScanDevicesState('DONE') - setDeviceId(device.id) - await startDiscoverServices() - await wait(1000) - setupOnDeviceDisconnected(device.id) - await wait(1000) - startCharacteristicMonitor(device.id) - await wait(1000) - const info = 'Now disconnect device' - console.info(info) - Toast.show({ - type: 'info', - text1: info - }) - } - }, - [deviceTimeService] - ) - } - - return ( - - - - - - setupOnDeviceDisconnected()} /> - - - - - - - - - startCharacteristicMonitor()} /> - - - - - ) -} diff --git a/example-expo/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx b/example-expo/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx deleted file mode 100644 index 99f21d898..000000000 --- a/example-expo/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { ScrollView } from 'react-native' -import { AppText, ScreenDefaultContainer } from '../../../components/atoms' -import type { MainStackParamList } from '../../../navigation/navigators' -import { BLEService } from '../../../services' - -type DeviceDetailsScreenProps = NativeStackScreenProps - -export function DeviceScreen(_props: DeviceDetailsScreenProps) { - const connectedDevice = BLEService.getDevice() - return ( - - - {JSON.stringify(connectedDevice, null, 4)} - - - ) -} diff --git a/example-expo/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx b/example-expo/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx deleted file mode 100644 index 01847a818..000000000 --- a/example-expo/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { BleError, Device, type Subscription, type DeviceId } from 'react-native-ble-plx' -import { ScrollView } from 'react-native' -import Toast from 'react-native-toast-message' -import { wait } from '../../../utils/wait' -import type { TestStateType } from '../../../types' -import { BLEService } from '../../../services' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { deviceTimeService } from '../../../consts/nRFDeviceConsts' - -type DeviceOnDisconnectTestScreenProps = NativeStackScreenProps - -export function DeviceOnDisconnectTestScreen(_props: DeviceOnDisconnectTestScreenProps) { - const [expectedDeviceName, setExpectedDeviceName] = useState('') - const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') - const [deviceId, setDeviceId] = useState('') - const [currentTest, setCurrentTest] = useState(null) - const onDisconnectRef = useRef(null) - - const checkDeviceName = (device: Device) => - device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase() - - const connectAndDiscover = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - await BLEService.discoverAllServicesAndCharacteristicsForDevice() - setTestScanDevicesState('DONE') - setDeviceId(device.id) - } - }, - [deviceTimeService] - ) - } - - const startConnectToDevice = (device: Device) => BLEService.connectToDevice(device.id) - - const setupOnDeviceDisconnected = useCallback( - (directDeviceId?: DeviceId) => { - if (!deviceId && !directDeviceId) { - console.error('Device not ready') - return - } - if (onDisconnectRef.current?.remove) { - onDisconnectRef.current.remove() - onDisconnectRef.current = null - } - onDisconnectRef.current = BLEService.onDeviceDisconnectedCustom(directDeviceId || deviceId, disconnectedListener) - console.info('on device disconnected ready') - }, - [deviceId] - ) - - const disconnectedListener = (error: BleError | null, device: Device | null) => { - console.warn('Disconnect listener called') - if (error) { - console.error('onDeviceDisconnected error') - } - if (device) { - console.info('onDeviceDisconnected device') - } - setDeviceId('') - setCurrentTest(null) - } - - // https://github.com/dotintent/react-native-ble-plx/issues/1126 - const start1126Test = () => connectAndDiscover().then(() => setCurrentTest('disconnectByPLX')) - - // https://github.com/dotintent/react-native-ble-plx/issues/1126 - const start1126DeviceTest = () => connectAndDiscover().then(() => setCurrentTest('disconnectByDevice')) - - const disconnectByPLX = useCallback(async () => { - try { - setupOnDeviceDisconnected() - await wait(1000) - console.info('expected warn: "Disconnect listener called"') - BLEService.disconnectDevice() - console.info('Finished') - } catch (error) { - console.error(error) - } - }, [setupOnDeviceDisconnected]) - - const disconnectByDevice = useCallback(async () => { - try { - setupOnDeviceDisconnected() - wait(1000) - Toast.show({ - type: 'info', - text1: 'Disconnect device', - text2: 'and expect warn: "Disconnect listener called"' - }) - console.info('Disconnect device and expect warn: "Disconnect listener called"') - } catch (error) { - console.error(error) - } - }, [setupOnDeviceDisconnected]) - - useEffect(() => { - if (!deviceId) { - return - } - if (currentTest === 'disconnectByPLX') { - disconnectByPLX() - } - if (currentTest === 'disconnectByDevice') { - disconnectByDevice() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [deviceId]) - - return ( - - - - - setupOnDeviceDisconnected()} /> - - start1126Test()} /> - start1126DeviceTest()} /> - - - ) -} diff --git a/example-expo/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx b/example-expo/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx deleted file mode 100644 index 1b3715b33..000000000 --- a/example-expo/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx +++ /dev/null @@ -1,694 +0,0 @@ -import React, { useState, type Dispatch } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { Device, type Base64 } from 'react-native-ble-plx' -import { Platform, ScrollView } from 'react-native' -import base64 from 'react-native-base64' -import type { TestStateType } from '../../../types' -import { BLEService } from '../../../services' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { wait } from '../../../utils/wait' -import { - currentTimeCharacteristic, - currentTimeCharacteristicTimeTriggerDescriptor, - currentTimeCharacteristicTimeTriggerDescriptorValue, - deviceTimeCharacteristic, - deviceTimeService, - monitorExpectedMessage, - writeWithResponseBase64Time, - writeWithoutResponseBase64Time -} from '../../../consts/nRFDeviceConsts' - -type DevicenRFTestScreenProps = NativeStackScreenProps - -export function DevicenRFTestScreen(_props: DevicenRFTestScreenProps) { - const [expectedDeviceName, setExpectedDeviceName] = useState('') - const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') - const [testDeviceConnectedState, setTestDeviceConnectedState] = useState('WAITING') - const [testDiscoverServicesAndCharacteristicsFoundState, setTestDiscoverServicesAndCharacteristicsFoundState] = - useState('WAITING') - - const [testDeviceTimeCharacteristicWriteWithResponseState, setTestDeviceTimeCharacteristicWriteWithResponseState] = - useState('WAITING') - const [ - testDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState - ] = useState('WAITING') - const [ - testDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseStateValue, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseStateValue - ] = useState('') - - const [ - testWriteDeviceTimeCharacteristicWithoutResponseForDeviceState, - setTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState - ] = useState('WAITING') - const [ - testReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState - ] = useState('WAITING') - const [ - testReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseStateValue, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseStateValue - ] = useState('') - - const [testReadTimeTriggerDescriptorForDeviceState, setTestReadTimeTriggerDescriptorForDeviceState] = - useState('WAITING') - const [testWriteTimeTriggerDescriptorForDeviceState, setTestWriteTimeTriggerDescriptorForDeviceState] = - useState('WAITING') - - const [testServicesForDeviceState, setTestServicesForDeviceState] = useState('WAITING') - const [testServicesForDeviceStateValue, setTestServicesForDeviceStateValue] = useState('') - - const [testCharacteristicsForDeviceState, setTestCharacteristicsForDeviceState] = useState('WAITING') - const [testCharacteristicsForDeviceStateValue, setTestCharacteristicsForDeviceStateValue] = useState('') - - const [testDescriptorsForDeviceState, setTestDescriptorsForDeviceState] = useState('WAITING') - const [testDescriptorsForDeviceStateValue, setTestDescriptorsForDeviceStateValue] = useState('') - - const [testIsDeviceConnectedStateState, setTestIsDeviceConnectedStateState] = useState('WAITING') - const [testOnDeviceDisconnectState, setTestOnDeviceDisconnectState] = useState('WAITING') - const [testConnectedDevicesState, setTestConnectedDevicesState] = useState('WAITING') - const [testRequestMTUForDeviceState, setTestRequestMTUForDeviceState] = useState('WAITING') - const [testCancelTransactionState, setTestCancelTransactionState] = useState('WAITING') - const [testReadRSSIForDeviceState, setTestReadRSSIForDeviceState] = useState('WAITING') - const [testGetDevicesState, setTestGetDevicesState] = useState('WAITING') - const [testBTStateState, setTestBTStateState] = useState('WAITING') - const [testRequestConnectionPriorityForDeviceState, setTestRequestConnectionPriorityForDeviceState] = - useState('WAITING') - const [testStartCancelDeviceConnectionState, setTestStartCancelDeviceConnectionState] = - useState('WAITING') - - const [testMonitorCurrentTimeCharacteristicForDevice, setTestMonitorCurrentTimeCharacteristicForDevice] = - useState('WAITING') - const [testDeviceDisconnectState, setTestDeviceDisconnectState] = useState('WAITING') - - const [testEnableState, setTestEnableState] = useState('WAITING') - const [testDisableState, setTestDisableState] = useState('WAITING') - - const onStartHandler = async () => { - setTestDeviceConnectedState('WAITING') - setTestDiscoverServicesAndCharacteristicsFoundState('WAITING') - setTestDeviceTimeCharacteristicWriteWithResponseState('WAITING') - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState('WAITING') - setTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState('WAITING') - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState('WAITING') - setTestReadTimeTriggerDescriptorForDeviceState('WAITING') - setTestWriteTimeTriggerDescriptorForDeviceState('WAITING') - setTestServicesForDeviceState('WAITING') - setTestCharacteristicsForDeviceState('WAITING') - setTestDescriptorsForDeviceState('WAITING') - setTestIsDeviceConnectedStateState('WAITING') - setTestOnDeviceDisconnectState('WAITING') - setTestConnectedDevicesState('WAITING') - setTestRequestMTUForDeviceState('WAITING') - setTestCancelTransactionState('WAITING') - setTestReadRSSIForDeviceState('WAITING') - setTestEnableState('WAITING') - setTestDisableState('WAITING') - setTestGetDevicesState('WAITING') - setTestMonitorCurrentTimeCharacteristicForDevice('WAITING') - setTestDeviceDisconnectState('WAITING') - setTestScanDevicesState('WAITING') - setTestStartCancelDeviceConnectionState('WAITING') - BLEService.initializeBLE().then(scanDevices) - } - - const onDeviceFound = (device: Device) => { - if (device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase()) { - setTestScanDevicesState('DONE') - startConnectToDevice(device) - .then(onDeviceDisconnected) - .then(startTestDiscoverServicesAndCharacteristicsFoundState) - .then(startWriteCharacteristicWithResponseForDevice) - .then(() => - startReadCharacteristicForDevice( - writeWithResponseBase64Time, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseStateValue - ) - ) - .then(startTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState) - .then(() => - startReadCharacteristicForDevice( - writeWithoutResponseBase64Time, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseStateValue - ) - ) - .then(startWriteTimeTriggerDescriptorForDevice) - .then(startReadTimeTriggerDescriptorForDevice) - .then(startTestGetServicesForDeviceState) - .then(startTestGetCharacteristicsForDeviceState) - .then(startTestGetDescriptorsForDeviceState) - .then(startIsDeviceConnectedState) - .then(startGetConnectedDevices) - .then(startRequestMTUForDevice) - .then(startTestCancelTransaction) - .then(startReadRSSIForDevice) - .then(startGetDevices) - .then(startGetState) - .then(startRequestConnectionPriorityForDevice) - .then(startTestMonitorCurrentTimeCharacteristicForDevice) - .then(disconnectDevice) - .then(startCancelDeviceConnection) - .then(startDisableEnableTest) - .catch(error => console.error(error.message)) - } - } - - const startTestInfo = (testName: string) => console.info('starting: ', testName) - - const runTest = (functionToRun: () => Promise, stateSetter: Dispatch, testName: string) => { - startTestInfo(testName) - stateSetter('IN_PROGRESS') - return functionToRun() - .then(() => { - console.info('success') - stateSetter('DONE') - }) - .catch(error => { - console.error(error) - stateSetter('ERROR') - }) - } - - const scanDevices = () => { - startTestInfo('scanDevices') - setTestScanDevicesState('IN_PROGRESS') - BLEService.scanDevices(onDeviceFound, [deviceTimeService]) - } - - const startConnectToDevice = (device: Device) => - runTest(() => BLEService.connectToDevice(device.id), setTestDeviceConnectedState, 'ConnectToDevice') - - const startTestDiscoverServicesAndCharacteristicsFoundState = () => - runTest( - BLEService.discoverAllServicesAndCharacteristicsForDevice, - setTestDiscoverServicesAndCharacteristicsFoundState, - 'startTestDiscoverServicesAndCharacteristicsFoundState' - ) - - const startWriteCharacteristicWithResponseForDevice = () => - runTest( - () => - BLEService.writeCharacteristicWithResponseForDevice( - deviceTimeService, - deviceTimeCharacteristic, - writeWithResponseBase64Time - ), - setTestDeviceTimeCharacteristicWriteWithResponseState, - 'startWriteCharacteristicWithResponseForDevice' - ) - - const startReadCharacteristicForDevice = ( - expectedValue: Base64, - stateSetFunction: Dispatch, - valueSetter: Dispatch - ) => { - startTestInfo('startReadCharacteristicForDevice') - stateSetFunction('IN_PROGRESS') - return BLEService.readCharacteristicForDevice(deviceTimeService, deviceTimeCharacteristic) - .then(characteristic => { - if (characteristic.value === expectedValue) { - stateSetFunction('DONE') - console.info('success') - valueSetter(characteristic.value) - } else { - throw new Error('Read error') - } - }) - .catch(error => { - console.error(error) - stateSetFunction('ERROR') - }) - } - - const startTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState = () => - runTest( - () => - BLEService.writeCharacteristicWithoutResponseForDevice( - deviceTimeService, - deviceTimeCharacteristic, - writeWithoutResponseBase64Time - ), - setTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState, - 'startTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState' - ) - - const startTestMonitorCurrentTimeCharacteristicForDevice = () => - new Promise((resolve, reject) => { - startTestInfo('startTestMonitorCurrentTimeCharacteristicForDevice') - setTestMonitorCurrentTimeCharacteristicForDevice('IN_PROGRESS') - BLEService.setupMonitor( - deviceTimeService, - currentTimeCharacteristic, - async characteristic => { - if (characteristic.value && base64.decode(characteristic.value) === monitorExpectedMessage) { - setTestMonitorCurrentTimeCharacteristicForDevice('DONE') - await BLEService.finishMonitor() - console.info('success') - resolve() - } - }, - async error => { - console.error(error) - setTestMonitorCurrentTimeCharacteristicForDevice('ERROR') - await BLEService.finishMonitor() - reject() - } - ) - }) - - const startWriteTimeTriggerDescriptorForDevice = () => - runTest( - () => - BLEService.writeDescriptorForDevice( - deviceTimeService, - currentTimeCharacteristic, - currentTimeCharacteristicTimeTriggerDescriptor, - currentTimeCharacteristicTimeTriggerDescriptorValue - ), - setTestWriteTimeTriggerDescriptorForDeviceState, - 'startWriteTimeTriggerDescriptorForDevice' - ) - - const startReadTimeTriggerDescriptorForDevice = () => { - setTestReadTimeTriggerDescriptorForDeviceState('IN_PROGRESS') - startTestInfo('startReadTimeTriggerDescriptorForDevice') - return BLEService.readDescriptorForDevice( - deviceTimeService, - currentTimeCharacteristic, - currentTimeCharacteristicTimeTriggerDescriptor - ) - .then(descriptor => { - if (descriptor?.value === currentTimeCharacteristicTimeTriggerDescriptorValue) { - setTestReadTimeTriggerDescriptorForDeviceState('DONE') - console.info('success') - } else { - throw new Error('Read error') - } - }) - .catch(error => { - console.error(error) - setTestReadTimeTriggerDescriptorForDeviceState('ERROR') - }) - } - - const startTestGetServicesForDeviceState = () => - runTest( - () => - BLEService.getServicesForDevice().then(services => { - if (!services) { - throw new Error('services error') - } - setTestServicesForDeviceStateValue( - JSON.stringify( - services.map(({ isPrimary, deviceID, id, uuid }) => ({ isPrimary, deviceID, id, uuid })), - null, - 4 - ) - ) - }), - setTestServicesForDeviceState, - 'startTestGetServicesForDeviceState' - ) - - const startTestGetCharacteristicsForDeviceState = () => - runTest( - () => - BLEService.getDescriptorsForDevice(deviceTimeService, currentTimeCharacteristic).then(descriptors => { - if (!descriptors) { - throw new Error('descriptors error') - } - setTestDescriptorsForDeviceStateValue( - JSON.stringify( - descriptors.map( - ({ deviceID, id, serviceID, serviceUUID, uuid, value, characteristicID, characteristicUUID }) => ({ - deviceID, - id, - serviceID, - serviceUUID, - uuid, - value, - characteristicID, - characteristicUUID - }) - ), - null, - 4 - ) - ) - }), - setTestCharacteristicsForDeviceState, - 'startTestGetCharacteristicsForDeviceState' - ) - - const startTestGetDescriptorsForDeviceState = () => - runTest( - () => - BLEService.getCharacteristicsForDevice(deviceTimeService).then(characteristics => { - if (!characteristics) { - throw new Error('characteristics error') - } - setTestCharacteristicsForDeviceStateValue( - JSON.stringify( - characteristics.map( - ({ - descriptors, - deviceID, - id, - isIndicatable, - isNotifiable, - isNotifying, - isReadable, - isWritableWithResponse, - isWritableWithoutResponse, - serviceID, - serviceUUID, - uuid, - value - }) => ({ - descriptors, - deviceID, - id, - isIndicatable, - isNotifiable, - isNotifying, - isReadable, - isWritableWithResponse, - isWritableWithoutResponse, - serviceID, - serviceUUID, - uuid, - value - }) - ), - null, - 4 - ) - ) - }), - setTestDescriptorsForDeviceState, - 'startTestGetDescriptorsForDeviceState' - ) - - const startIsDeviceConnectedState = () => - runTest( - () => - BLEService.isDeviceConnected().then(connectionStatus => { - if (!connectionStatus) { - throw new Error('isDeviceConnected error') - } - }), - setTestIsDeviceConnectedStateState, - 'startIsDeviceConnectedState' - ) - - const getConnectedDevices = () => - BLEService.getConnectedDevices([deviceTimeService]).then(connectedDevices => { - if (!connectedDevices) { - throw new Error('getConnectedDevices error') - } - const accurateDevice = connectedDevices.find(device => device.name === expectedDeviceName) - if (!accurateDevice) { - throw new Error('getConnectedDevices device not found') - } - return accurateDevice - }) - - const startGetConnectedDevices = () => - runTest(getConnectedDevices, setTestConnectedDevicesState, 'startGetConnectedDevices') - - const startRequestMTUForDevice = () => { - const expectedMTU = 40 - return runTest( - () => - BLEService.requestMTUForDevice(expectedMTU).then(device => { - if (Platform.OS === 'ios') { - return - } - if (!device) { - throw new Error('requestMTUForDevice error') - } - if (device.mtu !== expectedMTU) { - throw new Error('the requested MTU has not been set') - } - }), - setTestRequestMTUForDeviceState, - 'startRequestMTUForDevice' - ) - } - - const testCancelTransaction = async () => - new Promise((resolve, reject) => { - const transactionId = 'mtuRequestTransactionTestId' - BLEService.setupMonitor( - deviceTimeService, - currentTimeCharacteristic, - () => {}, - error => { - if (error.message === 'Operation was cancelled') { - resolve() - } else { - console.error(error) - } - }, - transactionId, - true - ) - BLEService.cancelTransaction(transactionId) - - setTimeout(() => reject(new Error('Cancel transaction timeout')), 5000) - }) - - const startTestCancelTransaction = () => - runTest(testCancelTransaction, setTestCancelTransactionState, 'startTestCancelTransaction') - - const disconnectDevice = () => runTest(BLEService.disconnectDevice, setTestDeviceDisconnectState, 'disconnectDevice') - - const startReadRSSIForDevice = () => - runTest( - () => - BLEService.readRSSIForDevice().then(device => { - if (!device) { - throw new Error('readRSSIForDevice error') - } - if (!device.rssi) { - throw new Error('readRSSIForDevice error') - } - }), - setTestReadRSSIForDeviceState, - 'startReadRSSIForDevice' - ) - - const startGetDevices = () => - runTest( - () => - BLEService.getDevices().then(devices => { - if (!devices) { - throw new Error('getDevices error') - } - const device = devices.filter(({ name }) => name === expectedDeviceName) - if (!device) { - throw new Error('getDevices error') - } - }), - setTestGetDevicesState, - 'startGetDevices' - ) - - const startRequestConnectionPriorityForDevice = () => - runTest( - () => - BLEService.requestConnectionPriorityForDevice(1).then(device => { - if (!device) { - throw new Error('getDevices error') - } - }), - setTestRequestConnectionPriorityForDeviceState, - 'startRequestConnectionPriorityForDevice' - ) - - const getState = () => - BLEService.getState().then(bluetoothState => { - if (!bluetoothState) { - throw new Error('getDevices error') - } - return bluetoothState - }) - - const startGetState = () => runTest(getState, setTestBTStateState, 'startGetState') - - const startDisableEnableTest = () => - // eslint-disable-next-line no-async-promise-executor - new Promise(async (resolve, reject) => { - startTestInfo('startDisableEnableTest') - setTestEnableState('IN_PROGRESS') - setTestDisableState('IN_PROGRESS') - if (parseInt(Platform.Version.toString(), 10) >= 33 || Platform.OS === 'ios') { - setTestEnableState('DONE') - setTestDisableState('DONE') - resolve() - return - } - const initialState = await BLEService.getState() - if (initialState === 'PoweredOff') { - await BLEService.enable() - wait(1000) - } - await BLEService.disable() - while (true) { - const expectedPoweredOffState = await BLEService.getState() - if (expectedPoweredOffState === 'Resetting') { - wait(1000) - continue - } - if (expectedPoweredOffState !== 'PoweredOff') { - reject(new Error('BT disable error')) - setTestDisableState('ERROR') - return - } - break - } - setTestDisableState('DONE') - await BLEService.enable() - while (true) { - const expectedPoweredOnState = await BLEService.getState() - if (expectedPoweredOnState === 'Resetting') { - wait(1000) - continue - } - if (expectedPoweredOnState !== 'PoweredOn') { - reject(new Error('BT enable error')) - setTestEnableState('ERROR') - return - } - break - } - setTestEnableState('DONE') - console.info('success') - resolve() - }) - - const onDeviceDisconnected = () => { - if (testOnDeviceDisconnectState === 'DONE') { - return - } - setTestOnDeviceDisconnectState('IN_PROGRESS') - const onDeviceDisconnectedSubscription = BLEService.onDeviceDisconnected((error, device) => { - if (error) { - setTestOnDeviceDisconnectState('ERROR') - } - if (device) { - setTestOnDeviceDisconnectState(prev => { - onDeviceDisconnectedSubscription.remove() - return prev === 'IN_PROGRESS' ? 'DONE' : 'ERROR' - }) - } - }) - } - - const cancelDeviceConnection = () => - new Promise((resolve, reject) => { - BLEService.scanDevices( - (device: Device) => { - if (device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase()) { - BLEService.connectToDevice(device.id) - .then(() => BLEService.cancelDeviceConnection()) - .then(() => resolve()) - .catch(error => { - if (error?.message === `Device ${device.id} was disconnected`) { - resolve() - } - reject(error) - }) - } - }, - [deviceTimeService] - ).catch(reject) - }) - - const startCancelDeviceConnection = () => - runTest(cancelDeviceConnection, setTestStartCancelDeviceConnectionState, 'startCancelDeviceConnection') - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/example-expo/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx b/example-expo/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx deleted file mode 100644 index 8cf621add..000000000 --- a/example-expo/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { BleError } from 'react-native-ble-plx' -import { ScrollView, View } from 'react-native' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppText, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { functionsToTest } from './utils' -import { BLEService } from '../../../services' -import { type TestStateType } from '../../../types' - -type DeviceConnectDisconnectTestScreenProps = NativeStackScreenProps -type TestData = { name: string; response: string | null } - -export function InstanceDestroyScreen(_props: DeviceConnectDisconnectTestScreenProps) { - const [dataReads, setDataReads] = useState<(TestData | string)[]>([]) - const [instanceExistsCalls, setInstanceExistsCalls] = useState(0) - const [instanceDestroyedCalls, setInstanceDestroyedCalls] = useState(0) - const [instanceDestroyedCallsWithCorrectInfo, setInstanceDestroyedCallsWithCorrectInfo] = useState(0) - const [correctInstaceDestroy, setCorrectInstaceDestroy] = useState('WAITING') - const [secondInstaceDestroyFinishedWithError, setSecondInstaceDestroyFinishedWithError] = - useState('WAITING') - - const callPromise = async (promise: Promise) => - promise - .then(value => { - const status = value?.toString() || 'finished' - console.info(status) - return status - }) - .catch((error: BleError) => { - const { reason } = error - if (reason) { - console.error(reason) - return reason - } - console.error(error) - return 'Error' - }) - - const startChain = async (increaseCounter: () => void) => { - for (let i = 0; i < functionsToTest.length; i += 1) { - try { - const testObject = functionsToTest[i] - if (testObject) { - const { name, functionToCall } = testObject - console.info(`${i} - ${name}`) - const response = await callPromise(functionToCall()) - if (response && response.includes('BleManager has been destroyed')) { - setInstanceDestroyedCallsWithCorrectInfo(prevState => prevState + 1) - } - addDataToTimeReads({ - name, - response - }) - increaseCounter() - } else { - addDataToTimeReads({ - name: `index-${i}`, - response: '-----ERROR-----' - }) - } - } catch (e) { - console.info(`PROBLEM WITH INDEX - ${i}`) - console.error(e) - } - } - } - - const addDataToTimeReads = ({ name, response }: TestData) => { - setDataReads(prevState => - prevState.concat({ - name, - response - }) - ) - } - - const startTest = async () => { - await startChain(() => setInstanceExistsCalls(prevState => prevState + 1)) - await BLEService.manager - .destroy() - .then(info => { - console.info('first destroy try - then', info) - setCorrectInstaceDestroy('DONE') - }) - .catch(error => { - console.error('first destroy try - catch', error) - setCorrectInstaceDestroy('ERROR') - }) - await BLEService.manager - .destroy() - .then(info => { - console.info('second destroy try - then', info) - setSecondInstaceDestroyFinishedWithError('ERROR') - }) - .catch(error => { - console.error('second destroy try - catch', error) - setSecondInstaceDestroyFinishedWithError( - error?.reason?.includes('BleManager has been destroyed') ? 'DONE' : 'ERROR' - ) - }) - - await startChain(() => setInstanceDestroyedCalls(prevState => prevState + 1)) - } - - const timeEntriesToRender = dataReads.map((entry, index) => { - if (typeof entry === 'object') { - const { name, response } = entry - - return ( - - {name} - result: {response} - - ) - } - - return ( - - {entry} - - ) - }) - - return ( - - - - It can get stuck on several functions per minute - - Finished calls with existing instance: {instanceExistsCalls}/{functionsToTest.length} - - - - - Finished calls with destroyed instance: {instanceDestroyedCalls}/{functionsToTest.length} - - - Finished calls with correct info about instance destroyed: {instanceDestroyedCallsWithCorrectInfo}/ - {functionsToTest.length} - - {timeEntriesToRender} - - - ) -} diff --git a/example-expo/src/screens/MainStack/InstanceDestroyScreen/utils.ts b/example-expo/src/screens/MainStack/InstanceDestroyScreen/utils.ts deleted file mode 100644 index 3b0de2068..000000000 --- a/example-expo/src/screens/MainStack/InstanceDestroyScreen/utils.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { ConnectionPriority, LogLevel } from 'react-native-ble-plx' -import { BLEService } from '../../../services' -import { deviceTimeCharacteristic, deviceTimeService } from '../../../consts/nRFDeviceConsts' - -const TEST_DEVICE_ID = '5F:0A:E8:F1:11:11' - -export const functionsToTest: { name: string; functionToCall: () => Promise }[] = [ - { - name: 'setLogLevel', - functionToCall: () => BLEService.manager.setLogLevel(LogLevel.Verbose) - }, - { - name: 'cancelTransaction', - functionToCall: () => BLEService.manager.cancelTransaction('transactionId') - }, - { - name: 'state', - functionToCall: BLEService.manager.state - }, - { - name: 'startDeviceScan', - functionToCall: () => BLEService.manager.startDeviceScan(null, null, () => {}) - }, - { - name: 'stopDeviceScan', - functionToCall: BLEService.manager.stopDeviceScan - }, - { - name: 'requestConnectionPriorityForDevice', - functionToCall: () => BLEService.manager.requestConnectionPriorityForDevice(TEST_DEVICE_ID, ConnectionPriority.High) - }, - { - name: 'readRSSIForDevice', - functionToCall: () => BLEService.manager.readRSSIForDevice(TEST_DEVICE_ID) - }, - { - name: 'requestMTUForDevice', - functionToCall: () => BLEService.manager.requestMTUForDevice(TEST_DEVICE_ID, 300) - }, - { - name: 'devices', - functionToCall: () => BLEService.manager.devices([TEST_DEVICE_ID]) - }, - { - name: 'connectedDevices', - functionToCall: () => BLEService.manager.connectedDevices([deviceTimeService]) - }, - { - name: 'connectToDevice', - functionToCall: () => BLEService.manager.connectToDevice(TEST_DEVICE_ID) - }, - { - name: 'cancelDeviceConnection', - functionToCall: () => BLEService.manager.cancelDeviceConnection(TEST_DEVICE_ID) - }, - { - name: 'isDeviceConnected', - functionToCall: () => BLEService.manager.isDeviceConnected(TEST_DEVICE_ID) - }, - { - name: 'discoverAllServicesAndCharacteristicsForDevice', - functionToCall: () => BLEService.manager.discoverAllServicesAndCharacteristicsForDevice(TEST_DEVICE_ID) - }, - { - name: 'servicesForDevice', - functionToCall: () => BLEService.manager.servicesForDevice(TEST_DEVICE_ID) - }, - { - name: 'characteristicsForDevice', - functionToCall: () => BLEService.manager.characteristicsForDevice(TEST_DEVICE_ID, deviceTimeService) - }, - { - name: 'descriptorsForDevice', - functionToCall: () => - BLEService.manager.descriptorsForDevice(TEST_DEVICE_ID, deviceTimeService, deviceTimeCharacteristic) - }, - { - name: 'readCharacteristicForDevice', - functionToCall: () => - BLEService.manager.readCharacteristicForDevice(TEST_DEVICE_ID, deviceTimeService, deviceTimeCharacteristic) - }, - { - name: 'writeCharacteristicWithResponseForDevice', - functionToCall: () => - BLEService.manager.writeCharacteristicWithResponseForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - 'base64Value' - ) - }, - { - name: 'writeCharacteristicWithoutResponseForDevice', - functionToCall: () => - BLEService.manager.writeCharacteristicWithoutResponseForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - 'base64Value' - ) - }, - { - name: 'readDescriptorForDevice', - functionToCall: () => - BLEService.manager.readDescriptorForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - deviceTimeCharacteristic - ) - }, - { - name: 'writeDescriptorForDevice', - functionToCall: () => - BLEService.manager.writeDescriptorForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - deviceTimeCharacteristic, - 'Base64' - ) - }, - { - name: 'disable', - functionToCall: () => BLEService.manager.disable() - }, - { - name: 'enable', - functionToCall: () => BLEService.manager.enable() - } -] as const diff --git a/example-expo/src/screens/MainStack/index.ts b/example-expo/src/screens/MainStack/index.ts deleted file mode 100644 index 6887aa49e..000000000 --- a/example-expo/src/screens/MainStack/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './DashboardScreen/DashboardScreen' -export * from './DeviceDetailsScreen/DeviceDetailsScreen' -export * from './DevicenRFTestScreen/DevicenRFTestScreen' -export * from './DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen' -export * from './InstanceDestroyScreen/InstanceDestroyScreen' -export * from './DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen' diff --git a/example-expo/src/screens/index.ts b/example-expo/src/screens/index.ts deleted file mode 100644 index 472fafd1e..000000000 --- a/example-expo/src/screens/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './MainStack' diff --git a/example-expo/src/services/BLEService/BLEService.ts b/example-expo/src/services/BLEService/BLEService.ts deleted file mode 100644 index 8b6e2a137..000000000 --- a/example-expo/src/services/BLEService/BLEService.ts +++ /dev/null @@ -1,456 +0,0 @@ -import { - BleError, - BleErrorCode, - BleManager, - Device, - State as BluetoothState, - LogLevel, - type DeviceId, - type TransactionId, - type UUID, - type Characteristic, - type Base64, - type Subscription -} from 'react-native-ble-plx' -import { PermissionsAndroid, Platform } from 'react-native' -import Toast from 'react-native-toast-message' - -const deviceNotConnectedErrorText = 'Device is not connected' - -class BLEServiceInstance { - manager: BleManager - - device: Device | null - - characteristicMonitor: Subscription | null - - isCharacteristicMonitorDisconnectExpected = false - - constructor() { - this.device = null - this.characteristicMonitor = null - this.manager = new BleManager() - this.manager.setLogLevel(LogLevel.Verbose) - } - - createNewManager = () => { - this.manager = new BleManager() - this.manager.setLogLevel(LogLevel.Verbose) - } - - getDevice = () => this.device - - initializeBLE = () => - new Promise(resolve => { - const subscription = this.manager.onStateChange(state => { - switch (state) { - case BluetoothState.Unsupported: - this.showErrorToast('') - break - case BluetoothState.PoweredOff: - this.onBluetoothPowerOff() - this.manager.enable().catch((error: BleError) => { - if (error.errorCode === BleErrorCode.BluetoothUnauthorized) { - this.requestBluetoothPermission() - } - }) - break - case BluetoothState.Unauthorized: - this.requestBluetoothPermission() - break - case BluetoothState.PoweredOn: - resolve() - subscription.remove() - break - default: - console.error('Unsupported state: ', state) - // resolve() - // subscription.remove() - } - }, true) - }) - - disconnectDevice = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .cancelDeviceConnection(this.device.id) - .then(() => this.showSuccessToast('Device disconnected')) - .catch(error => { - if (error?.code !== BleErrorCode.DeviceDisconnected) { - this.onError(error) - } - }) - } - - disconnectDeviceById = (id: DeviceId) => - this.manager - .cancelDeviceConnection(id) - .then(() => this.showSuccessToast('Device disconnected')) - .catch(error => { - if (error?.code !== BleErrorCode.DeviceDisconnected) { - this.onError(error) - } - }) - - onBluetoothPowerOff = () => { - this.showErrorToast('Bluetooth is turned off') - } - - scanDevices = async (onDeviceFound: (device: Device) => void, UUIDs: UUID[] | null = null, legacyScan?: boolean) => { - this.manager - .startDeviceScan(UUIDs, { legacyScan }, (error, device) => { - if (error) { - this.onError(error) - console.error(error.message) - this.manager.stopDeviceScan() - return - } - if (device) { - onDeviceFound(device) - } - }) - .then(() => {}) - .catch(console.error) - } - - connectToDevice = (deviceId: DeviceId) => - new Promise((resolve, reject) => { - this.manager.stopDeviceScan() - this.manager - .connectToDevice(deviceId) - .then(device => { - this.device = device - resolve(device) - }) - .catch(error => { - if (error.errorCode === BleErrorCode.DeviceAlreadyConnected && this.device) { - resolve(this.device) - } else { - this.onError(error) - reject(error) - } - }) - }) - - discoverAllServicesAndCharacteristicsForDevice = async () => - new Promise((resolve, reject) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - reject(new Error(deviceNotConnectedErrorText)) - return - } - this.manager - .discoverAllServicesAndCharacteristicsForDevice(this.device.id) - .then(device => { - resolve(device) - this.device = device - }) - .catch(error => { - this.onError(error) - reject(error) - }) - }) - - readCharacteristicForDevice = async (serviceUUID: UUID, characteristicUUID: UUID) => - new Promise((resolve, reject) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - reject(new Error(deviceNotConnectedErrorText)) - return - } - this.manager - .readCharacteristicForDevice(this.device.id, serviceUUID, characteristicUUID) - .then(characteristic => { - resolve(characteristic) - }) - .catch(error => { - this.onError(error) - }) - }) - - writeCharacteristicWithResponseForDevice = async (serviceUUID: UUID, characteristicUUID: UUID, time: Base64) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .writeCharacteristicWithResponseForDevice(this.device.id, serviceUUID, characteristicUUID, time) - .catch(error => { - this.onError(error) - }) - } - - writeCharacteristicWithoutResponseForDevice = async (serviceUUID: UUID, characteristicUUID: UUID, time: Base64) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .writeCharacteristicWithoutResponseForDevice(this.device.id, serviceUUID, characteristicUUID, time) - .catch(error => { - this.onError(error) - }) - } - - setupMonitor = ( - serviceUUID: UUID, - characteristicUUID: UUID, - onCharacteristicReceived: (characteristic: Characteristic) => void, - onError: (error: Error) => void, - transactionId?: TransactionId, - hideErrorDisplay?: boolean - ) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - this.characteristicMonitor = this.manager.monitorCharacteristicForDevice( - this.device?.id, - serviceUUID, - characteristicUUID, - (error, characteristic) => { - if (error) { - if (error.errorCode === 2 && this.isCharacteristicMonitorDisconnectExpected) { - this.isCharacteristicMonitorDisconnectExpected = false - return - } - onError(error) - if (!hideErrorDisplay) { - this.onError(error) - this.characteristicMonitor?.remove() - } - return - } - if (characteristic) { - onCharacteristicReceived(characteristic) - } - }, - transactionId - ) - } - - setupCustomMonitor: BleManager['monitorCharacteristicForDevice'] = (...args) => - this.manager.monitorCharacteristicForDevice(...args) - - finishMonitor = () => { - this.isCharacteristicMonitorDisconnectExpected = true - this.characteristicMonitor?.remove() - } - - writeDescriptorForDevice = async ( - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - data: Base64 - ) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .writeDescriptorForDevice(this.device.id, serviceUUID, characteristicUUID, descriptorUUID, data) - .catch(error => { - this.onError(error) - }) - } - - readDescriptorForDevice = async (serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .readDescriptorForDevice(this.device.id, serviceUUID, characteristicUUID, descriptorUUID) - .catch(error => { - this.onError(error) - }) - } - - getServicesForDevice = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.servicesForDevice(this.device.id).catch(error => { - this.onError(error) - }) - } - - getCharacteristicsForDevice = (serviceUUID: UUID) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.characteristicsForDevice(this.device.id, serviceUUID).catch(error => { - this.onError(error) - }) - } - - getDescriptorsForDevice = (serviceUUID: UUID, characteristicUUID: UUID) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.descriptorsForDevice(this.device.id, serviceUUID, characteristicUUID).catch(error => { - this.onError(error) - }) - } - - isDeviceConnected = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.isDeviceConnected(this.device.id) - } - - isDeviceWithIdConnected = (id: DeviceId) => this.manager.isDeviceConnected(id).catch(console.error) - - getConnectedDevices = (expectedServices: UUID[]) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.connectedDevices(expectedServices).catch(error => { - this.onError(error) - }) - } - - requestMTUForDevice = (mtu: number) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.requestMTUForDevice(this.device.id, mtu).catch(error => { - this.onError(error) - }) - } - - onDeviceDisconnected = (listener: (error: BleError | null, device: Device | null) => void) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.onDeviceDisconnected(this.device.id, listener) - } - - onDeviceDisconnectedCustom: BleManager['onDeviceDisconnected'] = (...args) => - this.manager.onDeviceDisconnected(...args) - - readRSSIForDevice = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.readRSSIForDevice(this.device.id).catch(error => { - this.onError(error) - }) - } - - getDevices = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.devices([this.device.id]).catch(error => { - this.onError(error) - }) - } - - cancelTransaction = (transactionId: TransactionId) => this.manager.cancelTransaction(transactionId) - - enable = () => - this.manager.enable().catch(error => { - this.onError(error) - }) - - disable = () => - this.manager.disable().catch(error => { - this.onError(error) - }) - - getState = () => - this.manager.state().catch(error => { - this.onError(error) - }) - - onError = (error: BleError) => { - switch (error.errorCode) { - case BleErrorCode.BluetoothUnauthorized: - this.requestBluetoothPermission() - break - case BleErrorCode.LocationServicesDisabled: - this.showErrorToast('Location services are disabled') - break - default: - this.showErrorToast(JSON.stringify(error, null, 4)) - } - } - - requestConnectionPriorityForDevice = (priority: 0 | 1 | 2) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.requestConnectionPriorityForDevice(this.device?.id, priority) - } - - cancelDeviceConnection = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.cancelDeviceConnection(this.device?.id) - } - - requestBluetoothPermission = async () => { - if (Platform.OS === 'ios') { - return true - } - if (Platform.OS === 'android') { - const apiLevel = parseInt(Platform.Version.toString(), 10) - - if (apiLevel < 31 && PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) - return granted === PermissionsAndroid.RESULTS.GRANTED - } - if (PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN && PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT) { - const result = await PermissionsAndroid.requestMultiple([ - PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, - PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT - ]) - - return ( - result['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED && - result['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED - ) - } - } - - this.showErrorToast('Permission have not been granted') - - return false - } - - showErrorToast = (error: string) => { - Toast.show({ - type: 'error', - text1: 'Error', - text2: error - }) - console.error(error) - } - - showSuccessToast = (info: string) => { - Toast.show({ - type: 'success', - text1: 'Success', - text2: info - }) - } -} - -export const BLEService = new BLEServiceInstance() diff --git a/example-expo/src/services/index.ts b/example-expo/src/services/index.ts deleted file mode 100644 index a4a07417a..000000000 --- a/example-expo/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BLEService/BLEService' diff --git a/example-expo/src/theme/colors.ts b/example-expo/src/theme/colors.ts deleted file mode 100644 index 7679fd50d..000000000 --- a/example-expo/src/theme/colors.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const colors = { - mainRed: '#ff304d' -} as const diff --git a/example-expo/src/theme/sizes.ts b/example-expo/src/theme/sizes.ts deleted file mode 100644 index 7c2ad4bf3..000000000 --- a/example-expo/src/theme/sizes.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const sizes = { - defaultFontSize: 14, - defaultScreenPadding: 12 -} as const diff --git a/example-expo/src/theme/theme.ts b/example-expo/src/theme/theme.ts deleted file mode 100644 index d5b1621b4..000000000 --- a/example-expo/src/theme/theme.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { DefaultTheme } from 'styled-components' -import { colors } from './colors' -import { sizes } from './sizes' - -export const commonTheme: DefaultTheme = { - sizes, - colors -} as const - -export type AppTheme = { sizes: typeof sizes; colors: typeof colors } diff --git a/example-expo/src/types/TestStateType.ts b/example-expo/src/types/TestStateType.ts deleted file mode 100644 index 93465a348..000000000 --- a/example-expo/src/types/TestStateType.ts +++ /dev/null @@ -1 +0,0 @@ -export type TestStateType = 'DONE' | 'WAITING' | 'ERROR' | 'IN_PROGRESS' diff --git a/example-expo/src/types/index.ts b/example-expo/src/types/index.ts deleted file mode 100644 index 3689e2fc7..000000000 --- a/example-expo/src/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TestStateType' diff --git a/example-expo/src/utils/cloneDeep.ts b/example-expo/src/utils/cloneDeep.ts deleted file mode 100644 index 93851e47f..000000000 --- a/example-expo/src/utils/cloneDeep.ts +++ /dev/null @@ -1 +0,0 @@ -export const cloneDeep: (objectToClone: T) => T = objectToClone => JSON.parse(JSON.stringify(objectToClone)) diff --git a/example-expo/src/utils/getCurrentTimeAsBase64.ts b/example-expo/src/utils/getCurrentTimeAsBase64.ts deleted file mode 100644 index 722beab2f..000000000 --- a/example-expo/src/utils/getCurrentTimeAsBase64.ts +++ /dev/null @@ -1,15 +0,0 @@ -import base64 from 'react-native-base64' -import { getDateUint8Array } from './getDateUint8Array' - -export const getCurrentDateAsBase64 = () => { - const date = new Date() - const dateToSend = getDateUint8Array( - date.getFullYear(), - date.getMonth(), - date.getDay(), - date.getHours(), - date.getMinutes(), - date.getSeconds() - ) - return base64.encodeFromByteArray(dateToSend) -} diff --git a/example-expo/src/utils/getDateAsBase64.ts b/example-expo/src/utils/getDateAsBase64.ts deleted file mode 100644 index cc091c085..000000000 --- a/example-expo/src/utils/getDateAsBase64.ts +++ /dev/null @@ -1,14 +0,0 @@ -import base64 from 'react-native-base64' -import { getDateUint8Array } from './getDateUint8Array' - -export const getDateAsBase64 = (date: Date) => { - const dateToSend = getDateUint8Array( - date.getFullYear(), - date.getMonth(), - date.getDay(), - date.getHours(), - date.getMinutes(), - date.getSeconds() - ) - return base64.encodeFromByteArray(dateToSend) -} diff --git a/example-expo/src/utils/getDateUint8Array.ts b/example-expo/src/utils/getDateUint8Array.ts deleted file mode 100644 index 6770bc112..000000000 --- a/example-expo/src/utils/getDateUint8Array.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const getDateUint8Array = ( - year: number, - month: number, - day: number, - hour: number, - minute: number, - second: number -) => { - const yearFirstByte = year >> 8 - const yearSecondByte = year - 2 ** 16 - - return new Uint8Array([yearFirstByte, yearSecondByte, month, day, hour, minute, second]) -} diff --git a/example-expo/src/utils/wait.ts b/example-expo/src/utils/wait.ts deleted file mode 100644 index 44bede3ae..000000000 --- a/example-expo/src/utils/wait.ts +++ /dev/null @@ -1 +0,0 @@ -export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/example-expo/tsconfig.json b/example-expo/tsconfig.json index b9567f605..2e9a6695e 100644 --- a/example-expo/tsconfig.json +++ b/example-expo/tsconfig.json @@ -1,6 +1,20 @@ { "extends": "expo/tsconfig.base", "compilerOptions": { - "strict": true - } + "strict": true, + "paths": { + "@/*": [ + "./src/*" + ], + "@/assets/*": [ + "./assets/*" + ] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ".expo/types/**/*.ts", + "expo-env.d.ts" + ] } diff --git a/example/.eslintrc.js b/example/.eslintrc.js new file mode 100644 index 000000000..187894b6a --- /dev/null +++ b/example/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native', +}; diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 000000000..de9995595 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,75 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +**/.xcode.env.local + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore +.kotlin/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +**/Pods/ +/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + +# testing +/coverage + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/example/.prettierrc.js b/example/.prettierrc.js new file mode 100644 index 000000000..06860c8d1 --- /dev/null +++ b/example/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + arrowParens: 'avoid', + singleQuote: true, + trailingComma: 'all', +}; diff --git a/example/App.tsx b/example/App.tsx new file mode 100644 index 000000000..834a527f7 --- /dev/null +++ b/example/App.tsx @@ -0,0 +1 @@ +export { default } from './src/App'; diff --git a/example/Gemfile b/example/Gemfile index da4befef3..6a4c5f171 100644 --- a/example/Gemfile +++ b/example/Gemfile @@ -7,4 +7,10 @@ ruby ">= 2.6.10" gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' gem 'xcodeproj', '< 1.26.0' -gem 'concurrent-ruby', '< 1.3.4' \ No newline at end of file +gem 'concurrent-ruby', '< 1.3.4' + +# Ruby 3.4.0 has removed some libraries from the standard library. +gem 'bigdecimal' +gem 'logger' +gem 'benchmark' +gem 'mutex_m' diff --git a/example/Gemfile.lock b/example/Gemfile.lock deleted file mode 100644 index 3ba40862b..000000000 --- a/example/Gemfile.lock +++ /dev/null @@ -1,118 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.8) - activesupport (7.2.3) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - claide (1.1.0) - cocoapods (1.15.2) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.2) - activesupport (>= 5.0, < 8) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (2.1) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.3.3) - connection_pool (3.0.2) - drb (2.2.3) - escape (0.0.4) - ethon (0.15.0) - ffi (>= 1.15.0) - ffi (1.17.3) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.9.0) - mutex_m - i18n (1.14.8) - concurrent-ruby (~> 1.0) - json (2.18.1) - logger (1.7.0) - minitest (6.0.1) - prism (~> 1.5) - molinillo (0.8.0) - mutex_m (0.3.0) - nanaimo (0.3.0) - nap (1.1.0) - netrc (0.11.0) - prism (1.9.0) - public_suffix (4.0.7) - rexml (3.4.4) - ruby-macho (2.5.1) - securerandom (0.4.1) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - xcodeproj (1.25.1) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (>= 3.3.6, < 4.0) - -PLATFORMS - ruby - -DEPENDENCIES - activesupport (>= 6.1.7.5, != 7.1.0) - cocoapods (>= 1.13, != 1.15.1, != 1.15.0) - concurrent-ruby (< 1.3.4) - xcodeproj (< 1.26.0) - -RUBY VERSION - ruby 3.2.0p0 - -BUNDLED WITH - 2.4.1 diff --git a/example/README.md b/example/README.md index 8bd066df0..3e2c3f850 100644 --- a/example/README.md +++ b/example/README.md @@ -2,58 +2,76 @@ This is a new [**React Native**](https://reactnative.dev) project, bootstrapped # Getting Started -> **Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. +> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. -## Step 1: Start the Metro Server +## Step 1: Start Metro -First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. +First, you will need to run **Metro**, the JavaScript build tool for React Native. -To start Metro, run the following command from the _root_ of your React Native project: +To start the Metro dev server, run the following command from the root of your React Native project: -```bash -# using npm +```sh +# Using npm npm start # OR using Yarn yarn start ``` -## Step 2: Start your Application +## Step 2: Build and run your app -Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: +With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: -### For Android +### Android -```bash -# using npm +```sh +# Using npm npm run android # OR using Yarn yarn android ``` -### For iOS +### iOS -```bash -# using npm +For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). + +The first time you create a new project, run the Ruby bundler to install CocoaPods itself: + +```sh +bundle install +``` + +Then, and every time you update your native dependencies, run: + +```sh +bundle exec pod install +``` + +For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). + +```sh +# Using npm npm run ios # OR using Yarn yarn ios ``` -If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. +If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. + +This is one way to run your app — you can also build it directly from Android Studio or Xcode. -This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. +## Step 3: Modify your app -## Step 3: Modifying your App +Now that you have successfully run the app, let's make changes! -Now that you have successfully run the app, let's modify it. +Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). -1. Open `App.tsx` in your text editor of choice and edit some lines. -2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! +When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: - For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! +- **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS). +- **iOS**: Press R in iOS Simulator. ## Congratulations! :tada: @@ -62,11 +80,11 @@ You've successfully run and modified your React Native App. :partying_face: ### Now what? - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). +- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). # Troubleshooting -If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. +If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. # Learn More diff --git a/example/__tests__/App.test.tsx b/example/__tests__/App.test.tsx new file mode 100644 index 000000000..e532f701e --- /dev/null +++ b/example/__tests__/App.test.tsx @@ -0,0 +1,13 @@ +/** + * @format + */ + +import React from 'react'; +import ReactTestRenderer from 'react-test-renderer'; +import App from '../App'; + +test('renders correctly', async () => { + await ReactTestRenderer.act(() => { + ReactTestRenderer.create(); + }); +}); diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 86c1a37db..3a8763468 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -19,9 +19,9 @@ react { /* Variants */ // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // skip the bundling of the JS bundle and the assets. Default is "debug", "debugOptimized". // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] + // debuggableVariants = ["liteDebug", "liteDebugOptimized", "prodDebug", "prodDebugOptimized"] /* Bundling */ // A list containing the node command and its flags. Default is just 'node'. @@ -63,14 +63,14 @@ def enableProguardInReleaseBuilds = false * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ -def jscFlavor = 'org.webkit:android-jsc:+' +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' android { ndkVersion rootProject.ext.ndkVersion diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 4b185bc15..000000000 --- a/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 16bf5a3e1..fb78f3974 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,15 +1,6 @@ - - - - - - - - - = - PackageList(this).packages.apply { - // Packages that cannot be autolinked yet can be added manually here, for example: - // add(MyReactNativePackage()) - } - - override fun getJSMainModuleName(): String = "index" - - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED - } - - override val reactHost: ReactHost - get() = getDefaultReactHost(applicationContext, reactNativeHost) + override val reactHost: ReactHost by lazy { + getDefaultReactHost( + context = applicationContext, + packageList = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + }, + ) + } override fun onCreate() { super.onCreate() - SoLoader.init(this, OpenSourceMergedSoMapping) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } + loadReactNative(this) } } diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index d435cfae5..7ba83a2ad 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,10 +1,9 @@ - diff --git a/example/android/build.gradle b/example/android/build.gradle index cd0e01a19..dad99b022 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,20 +1,20 @@ buildscript { ext { - buildToolsVersion = "35.0.0" + buildToolsVersion = "36.0.0" minSdkVersion = 24 - compileSdkVersion = 35 - targetSdkVersion = 34 - ndkVersion = "26.1.10909125" - kotlinVersion = "2.0.21" + compileSdkVersion = 36 + targetSdkVersion = 36 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.1.20" } repositories { google() mavenCentral() } dependencies { - classpath('com.android.tools.build:gradle') - classpath('com.facebook.react:react-native-gradle-plugin') - classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index a46a5b90f..9afe61598 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -21,8 +21,6 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true # Use this property to specify which architecture you want to build. # You can also override it from the CLI using @@ -34,8 +32,13 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=false +newArchEnabled=true # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. hermesEnabled=true + +# Use this property to enable edge-to-edge display support. +# This allows your app to draw behind system bars for an immersive UI. +# Note: Only works with ReactActivity and should not be used with custom Activity. +edgeToEdgeEnabled=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar index 7f93135c4..8bdaf60c7 100644 Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and b/example/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 79eb9d003..37f853b1c 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/example/android/gradlew b/example/android/gradlew index f5feea6d6..ef07e0162 100755 --- a/example/android/gradlew +++ b/example/android/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 7b21da3cd..7530e8d88 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -3,4 +3,4 @@ plugins { id("com.facebook.react.settings") } extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } rootProject.name = 'BlePlxExample' include ':app' -includeBuild('../node_modules/@react-native/gradle-plugin') \ No newline at end of file +includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/example/app.json b/example/app.json index 6d44d916e..32a4c7106 100644 --- a/example/app.json +++ b/example/app.json @@ -1,12 +1,4 @@ { "name": "BlePlxExample", - "displayName": "BlePlxExample", - "expo": { - "android": { - "package": "com.testProject" - }, - "ios": { - "bundleIdentifier": "com.testProject" - } - } + "displayName": "BlePlxExample" } diff --git a/example/babel.config.js b/example/babel.config.js index 7480acd17..f7b3da3b3 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -1,17 +1,3 @@ -const path = require('path') -const pak = require('../package.json') - module.exports = { presets: ['module:@react-native/babel-preset'], - plugins: [ - [ - 'module-resolver', - { - extensions: ['.tsx', '.ts', '.js', '.json'], - alias: { - [pak.name]: path.join(__dirname, '..', pak.source) - } - } - ] - ] -} +}; diff --git a/example/docs/nRFDeviceTesting/BLE-PLX-example.xml b/example/docs/nRFDeviceTesting/BLE-PLX-example.xml deleted file mode 100644 index 0c68f651f..000000000 --- a/example/docs/nRFDeviceTesting/BLE-PLX-example.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example/docs/nRFDeviceTesting/nRFDeviceTesting.md b/example/docs/nRFDeviceTesting/nRFDeviceTesting.md deleted file mode 100644 index 2c628e7b7..000000000 --- a/example/docs/nRFDeviceTesting/nRFDeviceTesting.md +++ /dev/null @@ -1,36 +0,0 @@ -# Getting Started - -## Setup and usage BLE device emulator - -To test the BLE-PLX library you will need: - -- BLE-PLX-example.xml (file is placed next to this file) -- nRF Connect - - https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=pl&gl=US - - https://apps.apple.com/pl/app/nrf-connect-for-mobile/id1054362403?l=pl - -To configure server: - -- open nRF Connect app -- go to the `Configure GATT server` screen -- open the import option from the top bar and import BLE-PLX-example.xml -- go to the `ADVERTISER` tab and setup advertising packet - - mark the `Connectable` checkbox - - name your advertiser `BLX-PLX-test` - - add new flags `Complete Local Name` record to `Advertising data` - - add new flags `Service UUID` record with `Device time` (0x1847) service to `Advertising data` - - add new flags `Complete Local Name` record to `Scan Response data` - - add new flags `Service UUID` record with `Device time` (0x1847) service to `Scan Response data` - - click `ok` button -- turn on your `BLX-PLX-test` advertiser -- go to the example app and click `Go to nRF test` - - type into `Device name to connect` your `Complete Local Name` from the server - - click `Start` (If your phone does not have the permissions to access BLE, it will ask for them first, after granting the permissions, click Start again) - - when app start `Monitor current time characteristic for device` test case, go back to your advertiser - - in the advertiser click `SERVER` tab - - open `Device Time` service - - click send icon next to `Current Time characteristic` - - send `Hi, it works!` value as TEXT(UTF-8) -- all step should finish as success with check mark icon - -step by step video: https://youtu.be/DZxEbOC8Sy4 diff --git a/example/index.js b/example/index.js index 2938510fe..9b7393291 100644 --- a/example/index.js +++ b/example/index.js @@ -1,5 +1,9 @@ -import { AppRegistry } from 'react-native' -import { App } from './src/App' -import { name as appName } from './app.json' +/** + * @format + */ -AppRegistry.registerComponent(appName, () => App) +import { AppRegistry } from 'react-native'; +import App from './App'; +import { name as appName } from './app.json'; + +AppRegistry.registerComponent(appName, () => App); diff --git a/example/ios/AppDelegate.swift b/example/ios/AppDelegate.swift deleted file mode 100644 index 2a2cc18dc..000000000 --- a/example/ios/AppDelegate.swift +++ /dev/null @@ -1,30 +0,0 @@ -import UIKit -import React -import React_RCTAppDelegate -import ReactAppDependencyProvider - -@main -class AppDelegate: RCTAppDelegate { - override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - self.moduleName = "BlePlxExample" - self.dependencyProvider = RCTAppDependencyProvider() - - // You can add your custom initial props in the dictionary below. - // They will be passed down to the ViewController used by React Native. - self.initialProps = [:] - - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - override func sourceURL(for bridge: RCTBridge) -> URL? { - self.bundleURL() - } - - override func bundleURL() -> URL? { -#if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") -#else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") -#endif - } -} \ No newline at end of file diff --git a/example/ios/BlePlxExample-Bridging-Header.h b/example/ios/BlePlxExample-Bridging-Header.h deleted file mode 100644 index e11d920b1..000000000 --- a/example/ios/BlePlxExample-Bridging-Header.h +++ /dev/null @@ -1,3 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// diff --git a/example/ios/BlePlxExample.xcodeproj/project.pbxproj b/example/ios/BlePlxExample.xcodeproj/project.pbxproj index 7af66a7ea..0d24bddcc 100644 --- a/example/ios/BlePlxExample.xcodeproj/project.pbxproj +++ b/example/ios/BlePlxExample.xcodeproj/project.pbxproj @@ -9,44 +9,25 @@ /* Begin PBXBuildFile section */ 0C80B921A6F3F58F76C31292 /* libPods-BlePlxExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-BlePlxExample.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 50D6A4712D4823CF003F04A9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D6A4702D4823CA003F04A9 /* AppDelegate.swift */; }; + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - AE8C3124373A144E7B92243D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 53F2F6E20F494004B6B46B83 /* PrivacyInfo.xcprivacy */; }; + C4AAD3DB0FF21C6F2BCA848D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = BlePlxExample; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ - 00E356EE1AD99517003FC87E /* BlePlxExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BlePlxExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* BlePlxExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlePlxExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = BlePlxExample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BlePlxExample/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = BlePlxExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 3B4392A12AC88292D35C810B /* Pods-BlePlxExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlePlxExample.debug.xcconfig"; path = "Target Support Files/Pods-BlePlxExample/Pods-BlePlxExample.debug.xcconfig"; sourceTree = ""; }; - 50D6A4702D4823CA003F04A9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 53F2F6E20F494004B6B46B83 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = BlePlxExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 5709B34CF0A7D63546082F79 /* Pods-BlePlxExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlePlxExample.release.xcconfig"; path = "Target Support Files/Pods-BlePlxExample/Pods-BlePlxExample.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-BlePlxExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BlePlxExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = BlePlxExample/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = BlePlxExample/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 00E356EB1AD99517003FC87E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -61,12 +42,11 @@ 13B07FAE1A68108700A75B9A /* BlePlxExample */ = { isa = PBXGroup; children = ( - 50D6A4702D4823CA003F04A9 /* AppDelegate.swift */, 13B07FB51A68108700A75B9A /* Images.xcassets */, + 761780EC2CA45674006654EE /* AppDelegate.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, - 53F2F6E20F494004B6B46B83 /* PrivacyInfo.xcprivacy */, ); name = BlePlxExample; sourceTree = ""; @@ -105,7 +85,6 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* BlePlxExample.app */, - 00E356EE1AD99517003FC87E /* BlePlxExampleTests.xctest */, ); name = Products; sourceTree = ""; @@ -122,24 +101,6 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 00E356ED1AD99517003FC87E /* BlePlxExampleTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "BlePlxExampleTests" */; - buildPhases = ( - 00E356EA1AD99517003FC87E /* Sources */, - 00E356EB1AD99517003FC87E /* Frameworks */, - 00E356EC1AD99517003FC87E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 00E356F51AD99517003FC87E /* PBXTargetDependency */, - ); - name = BlePlxExampleTests; - productName = BlePlxExampleTests; - productReference = 00E356EE1AD99517003FC87E /* BlePlxExampleTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 13B07F861A680F5B00A75B9A /* BlePlxExample */ = { isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "BlePlxExample" */; @@ -169,10 +130,6 @@ attributes = { LastUpgradeCheck = 1210; TargetAttributes = { - 00E356ED1AD99517003FC87E = { - CreatedOnToolsVersion = 6.2; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; 13B07F861A680F5B00A75B9A = { LastSwiftMigration = 1120; }; @@ -192,26 +149,18 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* BlePlxExample */, - 00E356ED1AD99517003FC87E /* BlePlxExampleTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 00E356EC1AD99517003FC87E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F8E1A680F5B00A75B9A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - AE8C3124373A144E7B92243D /* PrivacyInfo.xcprivacy in Resources */, + C4AAD3DB0FF21C6F2BCA848D /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,7 +181,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"\\\"$WITH_ENVIRONMENT\\\" \\\"$REACT_NATIVE_XCODE\\\"\"\n"; }; 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -293,93 +242,28 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 00E356EA1AD99517003FC87E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 50D6A4712D4823CF003F04A9 /* AppDelegate.swift in Sources */, + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* BlePlxExample */; - targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - DEVELOPMENT_TEAM = 457FBQ4469; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = BlePlxExampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.intent.BlePlxExample.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BlePlxExample.app/BlePlxExample"; - }; - name = Debug; - }; - 00E356F71AD99517003FC87E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - COPY_PHASE_STRIP = NO; - DEVELOPMENT_TEAM = 457FBQ4469; - INFOPLIST_FILE = BlePlxExampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.intent.BlePlxExample.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BlePlxExample.app/BlePlxExample"; - }; - name = Release; - }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-BlePlxExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = 457FBQ4469; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2974F4A5QH; ENABLE_BITCODE = NO; INFOPLIST_FILE = BlePlxExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -390,10 +274,12 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.intent.BlePlxExample; + PRODUCT_BUNDLE_IDENTIFIER = "com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-"; PRODUCT_NAME = BlePlxExample; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -404,9 +290,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = 457FBQ4469; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2974F4A5QH; INFOPLIST_FILE = BlePlxExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -417,9 +304,11 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.intent.BlePlxExample; + PRODUCT_BUNDLE_IDENTIFIER = "com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-"; PRODUCT_NAME = BlePlxExample; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -428,7 +317,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CC = ""; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; CLANG_CXX_LIBRARY = "libc++"; @@ -456,10 +344,9 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CXX = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -467,7 +354,6 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", - _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -476,9 +362,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD = ""; - LDPLUSPLUS = ""; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -490,7 +374,10 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "$(inherited)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -498,14 +385,12 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; }; name = Debug; @@ -514,7 +399,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CC = ""; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; CLANG_CXX_LIBRARY = "libc++"; @@ -542,25 +426,18 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CXX = ""; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, - ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD = ""; - LDPLUSPLUS = ""; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -571,7 +448,10 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "$(inherited)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -579,13 +459,11 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; VALIDATE_PRODUCT = YES; }; @@ -594,15 +472,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "BlePlxExampleTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "BlePlxExample" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/BlePlxExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/BlePlxExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/example/ios/BlePlxExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/example/ios/BlePlxExample/AppDelegate.swift b/example/ios/BlePlxExample/AppDelegate.swift new file mode 100644 index 000000000..a6a47a3cb --- /dev/null +++ b/example/ios/BlePlxExample/AppDelegate.swift @@ -0,0 +1,48 @@ +import UIKit +import React +import React_RCTAppDelegate +import ReactAppDependencyProvider + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + var reactNativeDelegate: ReactNativeDelegate? + var reactNativeFactory: RCTReactNativeFactory? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + let delegate = ReactNativeDelegate() + let factory = RCTReactNativeFactory(delegate: delegate) + delegate.dependencyProvider = RCTAppDependencyProvider() + + reactNativeDelegate = delegate + reactNativeFactory = factory + + window = UIWindow(frame: UIScreen.main.bounds) + + factory.startReactNative( + withModuleName: "BlePlxExample", + in: window, + launchOptions: launchOptions + ) + + return true + } +} + +class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { + override func sourceURL(for bridge: RCTBridge) -> URL? { + self.bundleURL() + } + + override func bundleURL() -> URL? { +#if DEBUG + RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + Bundle.main.url(forResource: "main", withExtension: "jsbundle") +#endif + } +} diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png deleted file mode 100644 index fd2b8ad25..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png deleted file mode 100644 index 1ff8bc713..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png deleted file mode 100644 index d1ea0e198..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png deleted file mode 100644 index 82c878aef..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png deleted file mode 100644 index 096ae9222..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png deleted file mode 100644 index 88180605a..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png deleted file mode 100644 index 88180605a..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png deleted file mode 100644 index 32f517b1c..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png deleted file mode 100644 index 548d4cc9a..000000000 Binary files a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png and /dev/null differ diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/Contents.json b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/Contents.json index eccc7eb10..81213230d 100644 --- a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,55 +1,46 @@ { "images" : [ { - "filename" : "AppIcon-20@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { - "filename" : "AppIcon-20@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { - "filename" : "AppIcon-29@2x~ipad.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { - "filename" : "AppIcon-29@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { - "filename" : "AppIcon-40@2x~ipad.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { - "filename" : "AppIcon-40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { - "filename" : "AppIcon-60@2x~car.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { - "filename" : "AppIcon-60@3x~car.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { - "filename" : "AppIcon~ios-marketing.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/example/ios/BlePlxExample/Info.plist b/example/ios/BlePlxExample/Info.plist index e18d713a9..452d36d56 100644 --- a/example/ios/BlePlxExample/Info.plist +++ b/example/ios/BlePlxExample/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion en CFBundleDisplayName @@ -32,9 +34,11 @@ NSBluetoothAlwaysUsageDescription - Our app uses bluetooth to find, connect and transfer data between different devices + This app uses Bluetooth to test BLE library functionality NSLocationWhenInUseUsageDescription + RCTNewArchEnabled + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -44,8 +48,13 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown UIViewControllerBasedStatusBarAppearance diff --git a/example/ios/File.swift b/example/ios/File.swift deleted file mode 100644 index 3222da5d1..000000000 --- a/example/ios/File.swift +++ /dev/null @@ -1,6 +0,0 @@ -// -// File.swift -// BlePlxExample -// - -import Foundation diff --git a/example/ios/Podfile b/example/ios/Podfile index fe022f153..55dc373ac 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -5,7 +5,7 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip -platform :ios, min_ios_version_supported +platform :ios, '17.0' prepare_react_native_project! linkage = ENV['USE_FRAMEWORKS'] @@ -24,12 +24,27 @@ target 'BlePlxExample' do ) post_install do |installer| - # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false, # :ccache_enabled => true ) + + # Force linker to include all ObjC classes from static libs. + # Without this, TurboModule classes (like BlePlx) that are only + # referenced via NSClassFromString get dead-stripped by the linker. + installer.aggregate_targets.each do |aggregate_target| + aggregate_target.user_project.native_targets.each do |target| + target.build_configurations.each do |config| + flags = config.build_settings['OTHER_LDFLAGS'] || ['$(inherited)'] + unless flags.include?('-ObjC') + flags << '-ObjC' + config.build_settings['OTHER_LDFLAGS'] = flags + end + end + end + aggregate_target.user_project.save + end end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 2ed71dd0f..061efaf41 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,75 +1,56 @@ PODS: - - boost (1.84.0) - - DoubleConversion (1.1.6) - - fast_float (6.1.4) - - FBLazyVector (0.77.0) - - fmt (11.0.2) - - glog (0.3.5) - - hermes-engine (0.77.0): - - hermes-engine/Pre-built (= 0.77.0) - - hermes-engine/Pre-built (0.77.0) - - MultiplatformBleAdapter (0.2.0) - - RCT-Folly (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Default (= 2024.11.18.00) - - RCT-Folly/Default (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Fabric (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - RCTDeprecation (0.77.0) - - RCTRequired (0.77.0) - - RCTTypeSafety (0.77.0): - - FBLazyVector (= 0.77.0) - - RCTRequired (= 0.77.0) - - React-Core (= 0.77.0) - - React (0.77.0): - - React-Core (= 0.77.0) - - React-Core/DevSupport (= 0.77.0) - - React-Core/RCTWebSocket (= 0.77.0) - - React-RCTActionSheet (= 0.77.0) - - React-RCTAnimation (= 0.77.0) - - React-RCTBlob (= 0.77.0) - - React-RCTImage (= 0.77.0) - - React-RCTLinking (= 0.77.0) - - React-RCTNetwork (= 0.77.0) - - React-RCTSettings (= 0.77.0) - - React-RCTText (= 0.77.0) - - React-RCTVibration (= 0.77.0) - - React-callinvoker (0.77.0) - - React-Core (0.77.0): - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) + - FBLazyVector (0.84.1) + - hermes-engine (250829098.0.9): + - hermes-engine/Pre-built (= 250829098.0.9) + - hermes-engine/Pre-built (250829098.0.9) + - RCTDeprecation (0.84.1) + - RCTRequired (0.84.1) + - RCTSwiftUI (0.84.1) + - RCTSwiftUIWrapper (0.84.1): + - RCTSwiftUI + - RCTTypeSafety (0.84.1): + - FBLazyVector (= 0.84.1) + - RCTRequired (= 0.84.1) + - React-Core (= 0.84.1) + - React (0.84.1): + - React-Core (= 0.84.1) + - React-Core/DevSupport (= 0.84.1) + - React-Core/RCTWebSocket (= 0.84.1) + - React-RCTActionSheet (= 0.84.1) + - React-RCTAnimation (= 0.84.1) + - React-RCTBlob (= 0.84.1) + - React-RCTImage (= 0.84.1) + - React-RCTLinking (= 0.84.1) + - React-RCTNetwork (= 0.84.1) + - React-RCTSettings (= 0.84.1) + - React-RCTText (= 0.84.1) + - React-RCTVibration (= 0.84.1) + - React-callinvoker (0.84.1) + - React-Core (0.84.1): + - hermes-engine - RCTDeprecation - - React-Core/Default (= 0.77.0) + - React-Core-prebuilt + - React-Core/Default (= 0.84.1) - React-cxxreact - React-featureflags - React-hermes - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/CoreModulesHeaders (0.77.0): - - glog + - React-Core-prebuilt (0.84.1): + - ReactNativeDependencies + - React-Core/CoreModulesHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -77,50 +58,56 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/Default (0.77.0): - - glog + - React-Core/Default (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-cxxreact - React-featureflags - React-hermes - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/DevSupport (0.77.0): - - glog + - React-Core/DevSupport (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation - - React-Core/Default (= 0.77.0) - - React-Core/RCTWebSocket (= 0.77.0) + - React-Core-prebuilt + - React-Core/Default (= 0.84.1) + - React-Core/RCTWebSocket (= 0.84.1) - React-cxxreact - React-featureflags - React-hermes - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTActionSheetHeaders (0.77.0): - - glog + - React-Core/RCTActionSheetHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -128,16 +115,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTAnimationHeaders (0.77.0): - - glog + - React-Core/RCTAnimationHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -145,16 +134,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTBlobHeaders (0.77.0): - - glog + - React-Core/RCTBlobHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -162,16 +153,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTImageHeaders (0.77.0): - - glog + - React-Core/RCTImageHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -179,16 +172,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTLinkingHeaders (0.77.0): - - glog + - React-Core/RCTLinkingHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -196,16 +191,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTNetworkHeaders (0.77.0): - - glog + - React-Core/RCTNetworkHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -213,16 +210,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTSettingsHeaders (0.77.0): - - glog + - React-Core/RCTSettingsHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -230,16 +229,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTTextHeaders (0.77.0): - - glog + - React-Core/RCTTextHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -247,16 +248,18 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTVibrationHeaders (0.77.0): - - glog + - React-Core/RCTVibrationHeaders (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -264,127 +267,215 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-Core/RCTWebSocket (0.77.0): - - glog + - React-Core/RCTWebSocket (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTDeprecation - - React-Core/Default (= 0.77.0) + - React-Core-prebuilt + - React-Core/Default (= 0.84.1) - React-cxxreact - React-featureflags - React-hermes - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling - React-perflogger + - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket (= 0.7.1) + - ReactNativeDependencies - Yoga - - React-CoreModules (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - RCT-Folly (= 2024.11.18.00) - - RCTTypeSafety (= 0.77.0) - - React-Core/CoreModulesHeaders (= 0.77.0) - - React-jsi (= 0.77.0) + - React-CoreModules (0.84.1): + - RCTTypeSafety (= 0.84.1) + - React-Core-prebuilt + - React-Core/CoreModulesHeaders (= 0.84.1) + - React-debug + - React-jsi (= 0.84.1) - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing - React-NativeModulesApple - React-RCTBlob - React-RCTFBReactNativeSpec - - React-RCTImage (= 0.77.0) + - React-RCTImage (= 0.84.1) + - React-runtimeexecutor + - React-utils - ReactCommon - - SocketRocket (= 0.7.1) - - React-cxxreact (0.77.0): - - boost - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-callinvoker (= 0.77.0) - - React-debug (= 0.77.0) - - React-jsi (= 0.77.0) + - ReactNativeDependencies + - React-cxxreact (0.84.1): + - hermes-engine + - React-callinvoker (= 0.84.1) + - React-Core-prebuilt + - React-debug (= 0.84.1) + - React-jsi (= 0.84.1) - React-jsinspector - - React-logger (= 0.77.0) - - React-perflogger (= 0.77.0) - - React-runtimeexecutor (= 0.77.0) - - React-timing (= 0.77.0) - - React-debug (0.77.0) - - React-defaultsnativemodule (0.77.0): - - hermes-engine - - RCT-Folly + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.84.1) + - React-perflogger (= 0.84.1) + - React-runtimeexecutor + - React-timing (= 0.84.1) + - React-utils + - ReactNativeDependencies + - React-debug (0.84.1) + - React-defaultsnativemodule (0.84.1): + - hermes-engine + - React-Core-prebuilt - React-domnativemodule + - React-featureflags - React-featureflagsnativemodule - React-idlecallbacksnativemodule + - React-intersectionobservernativemodule - React-jsi - React-jsiexecutor - React-microtasksnativemodule - React-RCTFBReactNativeSpec - - React-domnativemodule (0.77.0): + - React-webperformancenativemodule + - ReactNativeDependencies + - Yoga + - React-domnativemodule (0.84.1): - hermes-engine - - RCT-Folly + - React-Core-prebuilt - React-Fabric + - React-Fabric/bridging - React-FabricComponents - React-graphics - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec + - React-runtimeexecutor - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-Fabric (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-Fabric (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animated (= 0.84.1) + - React-Fabric/animationbackend (= 0.84.1) + - React-Fabric/animations (= 0.84.1) + - React-Fabric/attributedstring (= 0.84.1) + - React-Fabric/bridging (= 0.84.1) + - React-Fabric/componentregistry (= 0.84.1) + - React-Fabric/componentregistrynative (= 0.84.1) + - React-Fabric/components (= 0.84.1) + - React-Fabric/consistency (= 0.84.1) + - React-Fabric/core (= 0.84.1) + - React-Fabric/dom (= 0.84.1) + - React-Fabric/imagemanager (= 0.84.1) + - React-Fabric/leakchecker (= 0.84.1) + - React-Fabric/mounting (= 0.84.1) + - React-Fabric/observers (= 0.84.1) + - React-Fabric/scheduler (= 0.84.1) + - React-Fabric/telemetry (= 0.84.1) + - React-Fabric/templateprocessor (= 0.84.1) + - React-Fabric/uimanager (= 0.84.1) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animated (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animationbackend (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animations (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/attributedstring (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - - React-Fabric/animations (= 0.77.0) - - React-Fabric/attributedstring (= 0.77.0) - - React-Fabric/componentregistry (= 0.77.0) - - React-Fabric/componentregistrynative (= 0.77.0) - - React-Fabric/components (= 0.77.0) - - React-Fabric/core (= 0.77.0) - - React-Fabric/dom (= 0.77.0) - - React-Fabric/imagemanager (= 0.77.0) - - React-Fabric/leakchecker (= 0.77.0) - - React-Fabric/mounting (= 0.77.0) - - React-Fabric/observers (= 0.77.0) - - React-Fabric/scheduler (= 0.77.0) - - React-Fabric/telemetry (= 0.77.0) - - React-Fabric/templateprocessor (= 0.77.0) - - React-Fabric/uimanager (= 0.77.0) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/animations (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/bridging (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -393,19 +484,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/attributedstring (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/componentregistry (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -414,19 +503,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/componentregistry (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/componentregistrynative (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -435,64 +522,59 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/componentregistrynative (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/components (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.84.1) + - React-Fabric/components/root (= 0.84.1) + - React-Fabric/components/scrollview (= 0.84.1) + - React-Fabric/components/view (= 0.84.1) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/components/legacyviewmanagerinterop (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - - React-Fabric/components/legacyviewmanagerinterop (= 0.77.0) - - React-Fabric/components/root (= 0.77.0) - - React-Fabric/components/view (= 0.77.0) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/legacyviewmanagerinterop (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/components/root (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -501,19 +583,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/root (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/components/scrollview (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -522,19 +602,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/view (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/components/view (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -542,21 +620,58 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-renderercss - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-Fabric/core (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-Fabric/consistency (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/core (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/dom (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -565,19 +680,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/dom (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/imagemanager (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -586,19 +699,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/imagemanager (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/leakchecker (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -607,19 +718,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/leakchecker (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/mounting (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -628,62 +737,57 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/mounting (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/observers (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug + - React-Fabric/observers/events (= 0.84.1) + - React-Fabric/observers/intersection (= 0.84.1) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/observers (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/observers/events (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - - React-Fabric/observers/events (= 0.77.0) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/observers/events (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/observers/intersection (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -692,19 +796,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/scheduler (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/scheduler (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/observers/events @@ -713,21 +815,20 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-performancecdpmetrics - React-performancetimeline - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/telemetry (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/telemetry (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -736,19 +837,17 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/templateprocessor (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/templateprocessor (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -757,22 +856,20 @@ PODS: - React-jsiexecutor - React-logger - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/uimanager (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/uimanager (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - - React-Fabric/uimanager/consistency (= 0.77.0) + - React-Fabric/uimanager/consistency (= 0.84.1) - React-featureflags - React-graphics - React-jsi @@ -780,19 +877,17 @@ PODS: - React-logger - React-rendererconsistency - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/uimanager/consistency (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-Fabric/uimanager/consistency (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -802,76 +897,73 @@ PODS: - React-logger - React-rendererconsistency - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-FabricComponents (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-FabricComponents (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components (= 0.77.0) - - React-FabricComponents/textlayoutmanager (= 0.77.0) + - React-FabricComponents/components (= 0.84.1) + - React-FabricComponents/textlayoutmanager (= 0.84.1) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components/inputaccessory (= 0.77.0) - - React-FabricComponents/components/iostextinput (= 0.77.0) - - React-FabricComponents/components/modal (= 0.77.0) - - React-FabricComponents/components/rncore (= 0.77.0) - - React-FabricComponents/components/safeareaview (= 0.77.0) - - React-FabricComponents/components/scrollview (= 0.77.0) - - React-FabricComponents/components/text (= 0.77.0) - - React-FabricComponents/components/textinput (= 0.77.0) - - React-FabricComponents/components/unimplementedview (= 0.77.0) + - React-FabricComponents/components/inputaccessory (= 0.84.1) + - React-FabricComponents/components/iostextinput (= 0.84.1) + - React-FabricComponents/components/modal (= 0.84.1) + - React-FabricComponents/components/rncore (= 0.84.1) + - React-FabricComponents/components/safeareaview (= 0.84.1) + - React-FabricComponents/components/scrollview (= 0.84.1) + - React-FabricComponents/components/switch (= 0.84.1) + - React-FabricComponents/components/text (= 0.84.1) + - React-FabricComponents/components/textinput (= 0.84.1) + - React-FabricComponents/components/unimplementedview (= 0.84.1) + - React-FabricComponents/components/virtualview (= 0.84.1) + - React-FabricComponents/components/virtualviewexperimental (= 0.84.1) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/inputaccessory (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/inputaccessory (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -880,21 +972,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/iostextinput (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/iostextinput (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -903,21 +993,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/modal (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/modal (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -926,21 +1014,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/rncore (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/rncore (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -949,21 +1035,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/safeareaview (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/safeareaview (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -972,21 +1056,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/scrollview (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/scrollview (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -995,21 +1077,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/text (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/switch (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1018,21 +1098,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/textinput (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/text (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1041,21 +1119,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/components/unimplementedview (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/textinput (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1064,21 +1140,19 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricComponents/textlayoutmanager (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - React-FabricComponents/components/unimplementedview (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1087,253 +1161,397 @@ PODS: - React-jsi - React-jsiexecutor - React-logger + - React-RCTFBReactNativeSpec - React-rendererdebug - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-FabricImage (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - - RCTRequired (= 0.77.0) - - RCTTypeSafety (= 0.77.0) + - React-FabricComponents/components/virtualview (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/virtualviewexperimental (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/textlayoutmanager (0.84.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricImage (0.84.1): + - hermes-engine + - RCTRequired (= 0.84.1) + - RCTTypeSafety (= 0.84.1) + - React-Core-prebuilt - React-Fabric - React-featureflags - React-graphics - React-ImageManager - React-jsi - - React-jsiexecutor (= 0.77.0) + - React-jsiexecutor (= 0.84.1) - React-logger - React-rendererdebug - React-utils - ReactCommon + - ReactNativeDependencies - Yoga - - React-featureflags (0.77.0) - - React-featureflagsnativemodule (0.77.0): + - React-featureflags (0.84.1): + - React-Core-prebuilt + - ReactNativeDependencies + - React-featureflagsnativemodule (0.84.1): - hermes-engine - - RCT-Folly + - React-Core-prebuilt - React-featureflags - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - React-graphics (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Fabric (= 2024.11.18.00) + - ReactNativeDependencies + - React-graphics (0.84.1): + - hermes-engine + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-utils - - React-hermes (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog + - ReactNativeDependencies + - React-hermes (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-cxxreact (= 0.77.0) + - React-Core-prebuilt + - React-cxxreact (= 0.84.1) - React-jsi - - React-jsiexecutor (= 0.77.0) + - React-jsiexecutor (= 0.84.1) - React-jsinspector - - React-perflogger (= 0.77.0) + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-oscompat + - React-perflogger (= 0.84.1) - React-runtimeexecutor - - React-idlecallbacksnativemodule (0.77.0): + - ReactNativeDependencies + - React-idlecallbacksnativemodule (0.84.1): - hermes-engine - - RCT-Folly + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec + - React-runtimeexecutor - React-runtimescheduler - ReactCommon/turbomodule/core - - React-ImageManager (0.77.0): - - glog - - RCT-Folly/Fabric + - ReactNativeDependencies + - React-ImageManager (0.84.1): + - React-Core-prebuilt - React-Core/Default - React-debug - React-Fabric - React-graphics - React-rendererdebug - React-utils - - React-jserrorhandler (0.77.0): - - glog + - ReactNativeDependencies + - React-intersectionobservernativemodule (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-jserrorhandler (0.84.1): + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags - React-jsi - ReactCommon/turbomodule/bridging - - React-jsi (0.77.0): - - boost - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-jsiexecutor (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-cxxreact (= 0.77.0) - - React-jsi (= 0.77.0) + - ReactNativeDependencies + - React-jsi (0.84.1): + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsiexecutor (0.84.1): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-jserrorhandler + - React-jsi - React-jsinspector - - React-perflogger (= 0.77.0) - - React-jsinspector (0.77.0): - - DoubleConversion - - glog + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspector (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) + - React-Core-prebuilt - React-featureflags - React-jsi - - React-perflogger (= 0.77.0) - - React-runtimeexecutor (= 0.77.0) - - React-jsitracing (0.77.0): + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-oscompat + - React-perflogger (= 0.84.1) + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspectorcdp (0.84.1): + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsinspectornetwork (0.84.1): + - React-Core-prebuilt + - React-jsinspectorcdp + - ReactNativeDependencies + - React-jsinspectortracing (0.84.1): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsinspectornetwork + - React-oscompat + - React-timing + - ReactNativeDependencies + - React-jsitooling (0.84.1): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.84.1) + - React-debug + - React-jsi (= 0.84.1) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsitracing (0.84.1): - React-jsi - - React-logger (0.77.0): - - glog - - React-Mapbuffer (0.77.0): - - glog + - React-logger (0.84.1): + - React-Core-prebuilt + - ReactNativeDependencies + - React-Mapbuffer (0.84.1): + - React-Core-prebuilt - React-debug - - React-microtasksnativemodule (0.77.0): + - ReactNativeDependencies + - React-microtasksnativemodule (0.84.1): - hermes-engine - - RCT-Folly + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - react-native-ble-plx (3.5.1): - - DoubleConversion - - glog + - ReactNativeDependencies + - react-native-ble-plx (4.0.0-alpha.0): - hermes-engine - - MultiplatformBleAdapter (= 0.2.0) - - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager + - React-jsi - React-NativeModulesApple - React-RCTFabric + - React-renderercss - React-rendererdebug - React-utils - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - react-native-safe-area-context (5.1.0): - - DoubleConversion - - glog + - react-native-safe-area-context (5.7.0): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager - - react-native-safe-area-context/common (= 5.1.0) - - react-native-safe-area-context/fabric (= 5.1.0) + - React-jsi + - react-native-safe-area-context/common (= 5.7.0) + - react-native-safe-area-context/fabric (= 5.7.0) - React-NativeModulesApple - React-RCTFabric + - React-renderercss - React-rendererdebug - React-utils - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - react-native-safe-area-context/common (5.1.0): - - DoubleConversion - - glog + - react-native-safe-area-context/common (5.7.0): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager + - React-jsi - React-NativeModulesApple - React-RCTFabric + - React-renderercss - React-rendererdebug - React-utils - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - react-native-safe-area-context/fabric (5.1.0): - - DoubleConversion - - glog + - react-native-safe-area-context/fabric (5.7.0): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager + - React-jsi - react-native-safe-area-context/common - React-NativeModulesApple - React-RCTFabric + - React-renderercss - React-rendererdebug - React-utils - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - React-nativeconfig (0.77.0) - - React-NativeModulesApple (0.77.0): - - glog + - React-NativeModulesApple (0.84.1): - hermes-engine - React-callinvoker - React-Core + - React-Core-prebuilt - React-cxxreact + - React-debug + - React-featureflags - React-jsi - React-jsinspector + - React-jsinspectorcdp - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-perflogger (0.77.0): - - DoubleConversion - - RCT-Folly (= 2024.11.18.00) - - React-performancetimeline (0.77.0): - - RCT-Folly (= 2024.11.18.00) - - React-cxxreact + - ReactNativeDependencies + - React-networking (0.84.1): + - React-Core-prebuilt + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-timing + - ReactNativeDependencies + - React-oscompat (0.84.1) + - React-perflogger (0.84.1): + - React-Core-prebuilt + - ReactNativeDependencies + - React-performancecdpmetrics (0.84.1): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-performancetimeline + - React-runtimeexecutor + - React-timing + - ReactNativeDependencies + - React-performancetimeline (0.84.1): + - React-Core-prebuilt - React-featureflags + - React-jsinspector + - React-jsinspectortracing + - React-perflogger - React-timing - - React-RCTActionSheet (0.77.0): - - React-Core/RCTActionSheetHeaders (= 0.77.0) - - React-RCTAnimation (0.77.0): - - RCT-Folly (= 2024.11.18.00) + - ReactNativeDependencies + - React-RCTActionSheet (0.84.1): + - React-Core/RCTActionSheetHeaders (= 0.84.1) + - React-RCTAnimation (0.84.1): - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTAnimationHeaders + - React-debug + - React-featureflags - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - React-RCTAppDelegate (0.77.0): - - RCT-Folly (= 2024.11.18.00) + - ReactNativeDependencies + - React-RCTAppDelegate (0.84.1): + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-CoreModules - React-debug - React-defaultsnativemodule @@ -1341,38 +1559,39 @@ PODS: - React-featureflags - React-graphics - React-hermes - - React-nativeconfig + - React-jsitooling - React-NativeModulesApple - React-RCTFabric - React-RCTFBReactNativeSpec - React-RCTImage - React-RCTNetwork + - React-RCTRuntime - React-rendererdebug - React-RuntimeApple - React-RuntimeCore - - React-RuntimeHermes + - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon - - React-RCTBlob (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) + - ReactNativeDependencies + - React-RCTBlob (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) + - React-Core-prebuilt - React-Core/RCTBlobHeaders - React-Core/RCTWebSocket - React-jsi - React-jsinspector + - React-jsinspectorcdp - React-NativeModulesApple - React-RCTFBReactNativeSpec - React-RCTNetwork - ReactCommon - - React-RCTFabric (0.77.0): - - glog + - ReactNativeDependencies + - React-RCTFabric (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTSwiftUIWrapper - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricComponents @@ -1382,79 +1601,130 @@ PODS: - React-ImageManager - React-jsi - React-jsinspector - - React-nativeconfig + - React-jsinspectorcdp + - React-jsinspectortracing + - React-networking + - React-performancecdpmetrics - React-performancetimeline + - React-RCTAnimation + - React-RCTFBReactNativeSpec - React-RCTImage - React-RCTText - React-rendererconsistency + - React-renderercss - React-rendererdebug + - React-runtimeexecutor - React-runtimescheduler - React-utils + - ReactNativeDependencies - Yoga - - React-RCTFBReactNativeSpec (0.77.0): + - React-RCTFBReactNativeSpec (0.84.1): - hermes-engine - - RCT-Folly - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-jsi - - React-jsiexecutor - React-NativeModulesApple + - React-RCTFBReactNativeSpec/components (= 0.84.1) - ReactCommon - - React-RCTImage (0.77.0): - - RCT-Folly (= 2024.11.18.00) + - ReactNativeDependencies + - React-RCTFBReactNativeSpec/components (0.84.1): + - hermes-engine + - RCTRequired - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTImage (0.84.1): + - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTImageHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - React-RCTNetwork - ReactCommon - - React-RCTLinking (0.77.0): - - React-Core/RCTLinkingHeaders (= 0.77.0) - - React-jsi (= 0.77.0) + - ReactNativeDependencies + - React-RCTLinking (0.84.1): + - React-Core/RCTLinkingHeaders (= 0.84.1) + - React-jsi (= 0.84.1) - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - ReactCommon/turbomodule/core (= 0.77.0) - - React-RCTNetwork (0.77.0): - - RCT-Folly (= 2024.11.18.00) + - ReactCommon/turbomodule/core (= 0.84.1) + - React-RCTNetwork (0.84.1): - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTNetworkHeaders + - React-debug + - React-featureflags - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork - React-NativeModulesApple + - React-networking - React-RCTFBReactNativeSpec - ReactCommon - - React-RCTSettings (0.77.0): - - RCT-Folly (= 2024.11.18.00) + - ReactNativeDependencies + - React-RCTRuntime (0.84.1): + - hermes-engine + - React-Core + - React-Core-prebuilt + - React-debug + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-utils + - ReactNativeDependencies + - React-RCTSettings (0.84.1): - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTSettingsHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - React-RCTText (0.77.0): - - React-Core/RCTTextHeaders (= 0.77.0) + - ReactNativeDependencies + - React-RCTText (0.84.1): + - React-Core/RCTTextHeaders (= 0.84.1) - Yoga - - React-RCTVibration (0.77.0): - - RCT-Folly (= 2024.11.18.00) + - React-RCTVibration (0.84.1): + - React-Core-prebuilt - React-Core/RCTVibrationHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - React-rendererconsistency (0.77.0) - - React-rendererdebug (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - RCT-Folly (= 2024.11.18.00) + - ReactNativeDependencies + - React-rendererconsistency (0.84.1) + - React-renderercss (0.84.1): + - React-debug + - React-utils + - React-rendererdebug (0.84.1): + - React-Core-prebuilt - React-debug - - React-rncore (0.77.0) - - React-RuntimeApple (0.77.0): + - ReactNativeDependencies + - React-RuntimeApple (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) - React-callinvoker + - React-Core-prebuilt - React-Core/Default - React-CoreModules - React-cxxreact @@ -1463,6 +1733,7 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsitooling - React-Mapbuffer - React-NativeModulesApple - React-RCTFabric @@ -1472,10 +1743,10 @@ PODS: - React-RuntimeHermes - React-runtimescheduler - React-utils - - React-RuntimeCore (0.77.0): - - glog + - ReactNativeDependencies + - React-RuntimeCore (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) + - React-Core-prebuilt - React-cxxreact - React-Fabric - React-featureflags @@ -1483,55 +1754,77 @@ PODS: - React-jsi - React-jsiexecutor - React-jsinspector + - React-jsitooling - React-performancetimeline - React-runtimeexecutor - React-runtimescheduler - React-utils - - React-runtimeexecutor (0.77.0): - - React-jsi (= 0.77.0) - - React-RuntimeHermes (0.77.0): + - ReactNativeDependencies + - React-runtimeexecutor (0.84.1): + - React-Core-prebuilt + - React-debug + - React-featureflags + - React-jsi (= 0.84.1) + - React-utils + - ReactNativeDependencies + - React-RuntimeHermes (0.84.1): - hermes-engine - - RCT-Folly/Fabric (= 2024.11.18.00) + - React-Core-prebuilt - React-featureflags - React-hermes - React-jsi - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling - React-jsitracing - - React-nativeconfig - React-RuntimeCore + - React-runtimeexecutor - React-utils - - React-runtimescheduler (0.77.0): - - glog + - ReactNativeDependencies + - React-runtimescheduler (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - React-callinvoker + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags - React-jsi + - React-jsinspectortracing - React-performancetimeline - React-rendererconsistency - React-rendererdebug - React-runtimeexecutor - React-timing - React-utils - - React-timing (0.77.0) - - React-utils (0.77.0): - - glog + - ReactNativeDependencies + - React-timing (0.84.1): + - React-debug + - React-utils (0.84.1): - hermes-engine - - RCT-Folly (= 2024.11.18.00) + - React-Core-prebuilt - React-debug - - React-jsi (= 0.77.0) - - ReactAppDependencyProvider (0.77.0): + - React-jsi (= 0.84.1) + - ReactNativeDependencies + - React-webperformancenativemodule (0.84.1): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-performancetimeline + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactAppDependencyProvider (0.84.1): - ReactCodegen - - ReactCodegen (0.77.0): - - DoubleConversion - - glog + - ReactCodegen (0.84.1): - hermes-engine - - RCT-Folly - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricImage @@ -1545,134 +1838,105 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - ReactCommon (0.77.0): - - ReactCommon/turbomodule (= 0.77.0) - - ReactCommon/turbomodule (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-callinvoker (= 0.77.0) - - React-cxxreact (= 0.77.0) - - React-jsi (= 0.77.0) - - React-logger (= 0.77.0) - - React-perflogger (= 0.77.0) - - ReactCommon/turbomodule/bridging (= 0.77.0) - - ReactCommon/turbomodule/core (= 0.77.0) - - ReactCommon/turbomodule/bridging (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-callinvoker (= 0.77.0) - - React-cxxreact (= 0.77.0) - - React-jsi (= 0.77.0) - - React-logger (= 0.77.0) - - React-perflogger (= 0.77.0) - - ReactCommon/turbomodule/core (0.77.0): - - DoubleConversion - - fast_float (= 6.1.4) - - fmt (= 11.0.2) - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - React-callinvoker (= 0.77.0) - - React-cxxreact (= 0.77.0) - - React-debug (= 0.77.0) - - React-featureflags (= 0.77.0) - - React-jsi (= 0.77.0) - - React-logger (= 0.77.0) - - React-perflogger (= 0.77.0) - - React-utils (= 0.77.0) - - RNCAsyncStorage (2.1.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - RNScreens (4.5.0): - - DoubleConversion - - glog + - ReactNativeDependencies + - ReactCommon (0.84.1): + - React-Core-prebuilt + - ReactCommon/turbomodule (= 0.84.1) + - ReactNativeDependencies + - ReactCommon/turbomodule (0.84.1): + - hermes-engine + - React-callinvoker (= 0.84.1) + - React-Core-prebuilt + - React-cxxreact (= 0.84.1) + - React-jsi (= 0.84.1) + - React-logger (= 0.84.1) + - React-perflogger (= 0.84.1) + - ReactCommon/turbomodule/bridging (= 0.84.1) + - ReactCommon/turbomodule/core (= 0.84.1) + - ReactNativeDependencies + - ReactCommon/turbomodule/bridging (0.84.1): + - hermes-engine + - React-callinvoker (= 0.84.1) + - React-Core-prebuilt + - React-cxxreact (= 0.84.1) + - React-jsi (= 0.84.1) + - React-logger (= 0.84.1) + - React-perflogger (= 0.84.1) + - ReactNativeDependencies + - ReactCommon/turbomodule/core (0.84.1): + - hermes-engine + - React-callinvoker (= 0.84.1) + - React-Core-prebuilt + - React-cxxreact (= 0.84.1) + - React-debug (= 0.84.1) + - React-featureflags (= 0.84.1) + - React-jsi (= 0.84.1) + - React-logger (= 0.84.1) + - React-perflogger (= 0.84.1) + - React-utils (= 0.84.1) + - ReactNativeDependencies + - ReactNativeDependencies (0.84.1) + - RNScreens (4.24.0): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager + - React-jsi - React-NativeModulesApple - React-RCTFabric - React-RCTImage + - React-renderercss - React-rendererdebug - React-utils - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.5.0) + - ReactNativeDependencies + - RNScreens/common (= 4.24.0) - Yoga - - RNScreens/common (4.5.0): - - DoubleConversion - - glog + - RNScreens/common (4.24.0): - hermes-engine - - RCT-Folly (= 2024.11.18.00) - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager + - React-jsi - React-NativeModulesApple - React-RCTFabric - React-RCTImage + - React-renderercss - React-rendererdebug - React-utils - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - Yoga - - SocketRocket (0.7.1) - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) + - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Core (from `../node_modules/react-native/`) + - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) @@ -1688,19 +1952,26 @@ DEPENDENCIES: - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`) - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - - react-native-ble-plx (from `../..`) + - react-native-ble-plx (from `../node_modules/react-native-ble-plx`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`) - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -1711,12 +1982,13 @@ DEPENDENCIES: - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-rncore (from `../node_modules/react-native/ReactCommon`) - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) @@ -1724,40 +1996,28 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - - ReactAppDependencyProvider (from `build/generated/ios`) - - ReactCodegen (from `build/generated/ios`) + - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`) + - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) + - ReactCodegen (from `build/generated/ios/ReactCodegen`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) - RNScreens (from `../node_modules/react-native-screens`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) -SPEC REPOS: - trunk: - - MultiplatformBleAdapter - - SocketRocket - EXTERNAL SOURCES: - boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" - DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" - fast_float: - :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" - fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" - glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-2024-11-25-RNv0.77.0-d4f25d534ab744866448b36ca3bf3d97c08e638c - RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :tag: hermes-v250829098.0.9 RCTDeprecation: :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: :path: "../node_modules/react-native/Libraries/Required" + RCTSwiftUI: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUI" + RCTSwiftUIWrapper: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper" RCTTypeSafety: :path: "../node_modules/react-native/Libraries/TypeSafety" React: @@ -1766,6 +2026,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/callinvoker" React-Core: :path: "../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec" React-CoreModules: :path: "../node_modules/react-native/React/CoreModules" React-cxxreact: @@ -1794,6 +2056,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" React-ImageManager: :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-intersectionobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver" React-jserrorhandler: :path: "../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: @@ -1802,6 +2066,14 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../node_modules/react-native/ReactCommon/jsitooling" React-jsitracing: :path: "../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: @@ -1811,15 +2083,19 @@ EXTERNAL SOURCES: React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" react-native-ble-plx: - :path: "../.." + :path: "../node_modules/react-native-ble-plx" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" - React-nativeconfig: - :path: "../node_modules/react-native/ReactCommon" React-NativeModulesApple: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-networking: + :path: "../node_modules/react-native/ReactCommon/react/networking" + React-oscompat: + :path: "../node_modules/react-native/ReactCommon/oscompat" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancecdpmetrics: + :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics" React-performancetimeline: :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: @@ -1840,6 +2116,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: :path: "../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: "../node_modules/react-native/React/Runtime" React-RCTSettings: :path: "../node_modules/react-native/Libraries/Settings" React-RCTText: @@ -1848,10 +2126,10 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/Vibration" React-rendererconsistency: :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" React-rendererdebug: :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" - React-rncore: - :path: "../node_modules/react-native/ReactCommon" React-RuntimeApple: :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: @@ -1866,93 +2144,99 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/timing" React-utils: :path: "../node_modules/react-native/ReactCommon/react/utils" + React-webperformancenativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance" ReactAppDependencyProvider: - :path: build/generated/ios + :path: build/generated/ios/ReactAppDependencyProvider ReactCodegen: - :path: build/generated/ios + :path: build/generated/ios/ReactCodegen ReactCommon: :path: "../node_modules/react-native/ReactCommon" - RNCAsyncStorage: - :path: "../node_modules/@react-native-async-storage/async-storage" + ReactNativeDependencies: + :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" RNScreens: :path: "../node_modules/react-native-screens" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 - FBLazyVector: 2bc03a5cf64e29c611bbc5d7eb9d9f7431f37ee6 - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 - hermes-engine: 1f783c3d53940aed0d2c84586f0b7a85ab7827ef - MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 - RCTDeprecation: f5c19ebdb8804b53ed029123eb69914356192fc8 - RCTRequired: 6ae6cebe470486e0e0ce89c1c0eabb998e7c51f4 - RCTTypeSafety: 50d6ec72a3d13cf77e041ff43a0617050fb98e3f - React: e46fdbd82d2de942970c106677056f3bdd438d82 - React-callinvoker: b027ad895934b5f27ce166d095ed0d272d7df619 - React-Core: 92733c8280b1642afed7ebfb3c523feaec946ece - React-CoreModules: e2dfd87b6fdb9d969b16871655885a4d89a2a9f4 - React-cxxreact: d1a70e78543bb5b159fdaf6c52cadd33c1ae3244 - React-debug: 78d7544d2750737ac3acc88cca2f457d081ec43d - React-defaultsnativemodule: b24e61fe2d5bb84501898683f9d13ff7fc02a9df - React-domnativemodule: 210ca3670f16ae92fbcff8da204750af8a7295af - React-Fabric: 4b3d03ea38646dcc80888253c2befca80526abed - React-FabricComponents: 38fcb6f5c08f8de9e693f2644d2da54ae4fbf6c8 - React-FabricImage: 1d37769002c13dfffa9f53557a173d56c9ade5e3 - React-featureflags: 92dd7d0169ab0bf8ad404a5fe757c1ca7ccd74e8 - React-featureflagsnativemodule: 8a6373d7b4ef3c08d82b60376f75bd189bfc8cb2 - React-graphics: 2b316fcf5b6c29ded7d53ae0007d1d129dc89510 - React-hermes: bf50c8272cb562300a54a621aa69dc12a0b4fcf2 - React-idlecallbacksnativemodule: 47df5b6649ca5e0046aa3e43e680452007b16871 - React-ImageManager: 83b8dc67e97cd5fe10cb715bd878aded16adb40f - React-jserrorhandler: ac08c5673dea69b08e11faf074fd602fbf9492cc - React-jsi: 19e77567e235d06b7e8f425d2a6c1e948ab286e9 - React-jsiexecutor: fe6ad8b9a2bf97e435fc1c969c80ed7f447ed68e - React-jsinspector: f321d958a5534b65b56f7806c674e159c28f7d69 - React-jsitracing: d358876acde46009f391228b932a5efe13c8895b - React-logger: 02e5802824aa9b15cb7df42e10a91abead83cd8d - React-Mapbuffer: 99bd566147aaa78e872568be53ebca8a4449ddae - React-microtasksnativemodule: 51e7813abf875408a0f367e473a65bbab6aa8481 - react-native-ble-plx: f0262602d5a28a77afd46fa781d6340489b30387 - react-native-safe-area-context: efd435f89b73d91f37438e5ac2d725f0e7adff95 - React-nativeconfig: cd0fbb40987a9658c24dab5812c14e5522a64929 - React-NativeModulesApple: 4a9c304aa4fb086af32e8758ba892386d895b4d3 - React-perflogger: 721172bda31a65ce7b7a0c3bf3de96f12ef6f45d - React-performancetimeline: 46dbe9fd618ff882f59600dcd9fa923a9713cc3b - React-RCTActionSheet: 25eb72eabade4095bfaf6cd9c5c965c76865daa8 - React-RCTAnimation: 8efbd0a4a71fd3dbe84e6d08b92bec5728b7524b - React-RCTAppDelegate: 8ff6da817adefd15d4e25ade53a477c344f9b213 - React-RCTBlob: 6056bd62a56a6d2dad55cdf195949db1de623e14 - React-RCTFabric: 949589de63c19b8b197555567fbc51eebd265bbc - React-RCTFBReactNativeSpec: 4214925b1c4829fb1e73bfbacb301244b522dc11 - React-RCTImage: 7b3f38c77e183bdcb43dbcd7b5842b96c814889a - React-RCTLinking: 6cca74db71b23f670b72e45603e615c2b72b2235 - React-RCTNetwork: 5791b0718eff20c12f6f3d62e2ad50cff4b5c8a0 - React-RCTSettings: 84154e31a232b5b03b6b7a89924a267c431ccf16 - React-RCTText: cd49cb4442ee7f64b0415b27745d2495cb40cfaa - React-RCTVibration: 2a7432e61d42f802716bd67edc793b5e5f58971a - React-rendererconsistency: 7a81b08f01655b458d1de48ddd5b3f5988fd753f - React-rendererdebug: a6547cf2f3f7bcdd8d36ff5e103145d83f5001d4 - React-rncore: dd08c91cea25486f79012e32975c0ea26bd92760 - React-RuntimeApple: ea09b4c38df2695e0cb3fa60a83db81d653a39fd - React-RuntimeCore: 3dc763d365a1f738d92cd942066dd347953733f3 - React-runtimeexecutor: f9ae11481be048438640085c1e8266d6afebae44 - React-RuntimeHermes: 3bc16b5a5a756a292ad6f56968dfb8de643ae20b - React-runtimescheduler: 2e90401c400b62bb720d6ac028dcef803e30d888 - React-timing: 0d0263a5d8ab6fc8c325efb54cee1d6a6f01d657 - React-utils: 8905cd01f46755ea42268875d04c614a0d46431e - ReactAppDependencyProvider: 6e8d68583f39dc31ee65235110287277eb8556ef - ReactCodegen: c08a5113d9c9c895fe10f3c296f74c6b705a60a9 - ReactCommon: 1bd2dc684d7992acbf0dfee887b89a57a1ead86d - RNCAsyncStorage: 73773ca1dd29378bf0fe8dd7d67870d422e2fc05 - RNScreens: 5d61e452b51e7c23b3fcb9f16c4967d683a60a9d - SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 78d74e245ed67bb94275a1316cdc170b9b7fe884 + FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da + hermes-engine: bffb396beafa055b7a3f4773a03cf5103a95c680 + RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7 + RCTRequired: bb77b070f75f53398ce43c0aaaa58337cebe2bf6 + RCTSwiftUI: afc0a0a635860da1040a0b894bfd529da06d7810 + RCTSwiftUIWrapper: 3197c020094f3b2151bb2d1223f7276787be8166 + RCTTypeSafety: d13e192a37f151ce354641184bf4239844a3be17 + React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b + React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b + React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f + React-Core-prebuilt: 7b9bcb3ec23976d1996c1b89575edc04dc602845 + React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42 + React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe + React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc + React-defaultsnativemodule: bb85b1bdd9b4b82650cfa92998567fcfdb030145 + React-domnativemodule: ffdba8ba4323387e821d8298be2013516036df87 + React-Fabric: 8705ba7f14acf5b1df474d1af4b0191c69ac4690 + React-FabricComponents: 5a1b5007fe8c5d5f043e45c335ebef2af0717fb2 + React-FabricImage: e96eea6bb65b501cf5db3ce4ad057a97dea1dd69 + React-featureflags: 410f6c383eb94019f63f105374d738169df291ae + React-featureflagsnativemodule: 5d6d7931ec5d4576639661c0f79169a0ae383023 + React-graphics: b9b69adbe79d6944838aa304c849d78f977e9f21 + React-hermes: 666c66bbc856b46dfa4b132f1c9efaa37ad419a1 + React-idlecallbacksnativemodule: 785d307b9236ec9fb4ec0bcf99b34e8539d2d76e + React-ImageManager: daeef8b1c19803c71a9233f21f7f235cd3ec294e + React-intersectionobservernativemodule: c17189d2350205012682aefe566f7ebaa4f980e3 + React-jserrorhandler: 431377b3d1783127bc394986fe8d932bcb8982fe + React-jsi: 33db13b95bb53827b03d9fb0f567d12b63dfc8ef + React-jsiexecutor: 49de4d48a7c4f283eaa865f7a4f3919f2c39be1c + React-jsinspector: 3ec7dd478806a0eb4ec6ab394e51a395e8f895ba + React-jsinspectorcdp: bcb79a666959b1a3e8aac3ff8209d05c719aaa2a + React-jsinspectornetwork: be3b81a6342b56c74dd0bdd58bf3f62f978c1472 + React-jsinspectortracing: 293251deadf7c255da593bf1d9d337a645abdfdc + React-jsitooling: 6f729cdb85ff0c8294709ec1903e1f0c59662331 + React-jsitracing: be95d903cc9440ab89a704c999dd7db6675dc47f + React-logger: b5521614afb8690420146dfc61a1447bb5d65419 + React-Mapbuffer: f4ee8c62e0ef8359d139124f35a471518a172cd3 + React-microtasksnativemodule: d1956f0eec54c619b63a379520fb4c618a55ccb9 + react-native-ble-plx: 30ec90c1d8688ad65b395745b57087019eb7585f + react-native-safe-area-context: ae7587b95fb580d1800c5b0b2a7bd48c2868e67a + React-NativeModulesApple: 5ba0903927f6b8d335a091700e9fda143980f819 + React-networking: 3a4b7f9ed2b2d1c0441beacb79674323a24bcca6 + React-oscompat: ff26abf0ae3e3fdbe47b44224571e3fc7226a573 + React-perflogger: a86b2146936f2ffa80188425c6e8892729e2ad01 + React-performancecdpmetrics: 65b699c3e52c0a1d978d9cdf15e60c62e335b900 + React-performancetimeline: b44f82caa563e46068d02d9cf5b0d2b84bdc7a6a + React-RCTActionSheet: fc1d5d419856868e7f8c13c14591ed63dadef43a + React-RCTAnimation: 2a1e7eeb55b71e8524db296fa31e46eeaa2d0da4 + React-RCTAppDelegate: 317c1102a2d0bcc07567d8de58d9147102080b0e + React-RCTBlob: c82bbf96b2d1389550c05fb9739d4e85d471052e + React-RCTFabric: d82ad8120ba70fe63207e261b9e33d9b6077e2de + React-RCTFBReactNativeSpec: 4d33b5f3b339e9fa902168e8a1d62c458dda16e9 + React-RCTImage: f959463e53ea731ab267e371eea1b92fccf27634 + React-RCTLinking: 2a857113b8059ac4f0a8ae89f8aa312837b955d0 + React-RCTNetwork: dc026bf25f6457249349be8cf8bd5fdf84cdfb14 + React-RCTRuntime: b0630731fd864064066301e1e31406fea606d5f9 + React-RCTSettings: e159e19475df2e92fd3b4ddcfe29f6cc12cf0ee4 + React-RCTText: 7356cc84c2ed79635931891c176f72e0289dc75d + React-RCTVibration: 77621175b67b22e655ce4b29d1cda8498f2033e7 + React-rendererconsistency: e91aba4bb482dac127ad955dba6333a8af629c5b + React-renderercss: 7cc41efaecf557d7b70edaa08fad5ace79f714f6 + React-rendererdebug: 0f004cbed7b4c27327423be47209770830bf3c6d + React-RuntimeApple: 6f4ff8e2d8b05cb3ceabf57e494c04da8751f009 + React-RuntimeCore: 25be9c7025eabb524cd00dbb6ce56d6b122e3d92 + React-runtimeexecutor: 84d394b9f0a8fc7dab8a98bf88a43228bb04dac2 + React-RuntimeHermes: 2bed5b2d2419945cc5c2f6d627a1b46ce3a0f66a + React-runtimescheduler: 23b092dbcd3088f9c947551c23366dd00254caf3 + React-timing: 2ab9ccd4b41aa171090c16f664f6c5bfb2fd0ddc + React-utils: 8d888b379f0808bfabaea03d85f9e8dd9b8548da + React-webperformancenativemodule: c10016db7f1bb1153060d4aa9f7dbde2c88c845d + ReactAppDependencyProvider: e96e93b493d8d86eeaee3e590ba0be53f6abe46f + ReactCodegen: 797de5178718324c6eba3327b07f9a423fbd5787 + ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e + ReactNativeDependencies: 95fca8406ec78f74aaacae8490ad87795c8eab5b + RNScreens: 6cb648bdad8fe9bee9259fe144df95b6d1d5b707 + Yoga: c0b3f2c7e8d3e327e450223a2414ca3fa296b9a2 -PODFILE CHECKSUM: 1f6b8dae8c618b21f01439456de8661dd328d9b9 +PODFILE CHECKSUM: 7e0e156ac04ee33f77f15e2d84fa17c2ff0698ac -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/jest.config.js b/example/jest.config.js index 3d869b20d..8eb675e9b 100644 --- a/example/jest.config.js +++ b/example/jest.config.js @@ -1,3 +1,3 @@ module.exports = { - preset: 'react-native' -} + preset: 'react-native', +}; diff --git a/example/libs/styled.d.ts b/example/libs/styled.d.ts deleted file mode 100644 index 009b32e89..000000000 --- a/example/libs/styled.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { AppTheme } from '../src/theme/theme' -import 'styled-components' - -// Allows for type checking of theme in styled-components and IntelliSense support -declare module 'styled-components' { - export interface DefaultTheme extends AppTheme {} -} diff --git a/example/metro.config.js b/example/metro.config.js index 0012cbac7..525b2a113 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -1,40 +1,45 @@ -const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config') -const path = require('path') -const escape = require('escape-string-regexp') -const exclusionList = require('metro-config/src/defaults/exclusionList') -const pak = require('../package.json') +const path = require('path'); +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); -const root = path.resolve(__dirname, '..') -const modules = Object.keys({ ...pak.peerDependencies }) +const libraryRoot = path.resolve(__dirname, '..'); +const exampleNodeModules = path.resolve(__dirname, 'node_modules'); + +// Packages that must resolve from the example app's node_modules so that there +// is only a single copy at runtime (avoids version mismatches between the +// library's devDependencies and the example app's dependencies). +const forcedModules = ['react', 'react-native']; /** * Metro configuration - * https://facebook.github.io/metro/docs/configuration + * https://reactnative.dev/docs/metro * * @type {import('@react-native/metro-config').MetroConfig} */ const config = { - watchFolders: [root], - - // We need to make sure that only one version is loaded for peerDependencies - // So we block them at the root, and alias them to the versions in example's node_modules + watchFolders: [libraryRoot], resolver: { - blacklistRE: exclusionList(modules.map(m => new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`))), - - extraNodeModules: modules.reduce((acc, name) => { - acc[name] = path.join(__dirname, 'node_modules', name) - return acc - }, {}) - }, - - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true + // Make sure Metro can resolve modules from the library root + // Only resolve from the example app's node_modules — the library root + // should NOT have its own node_modules installed (prevents duplicate + // react-native versions causing TurboModule resolution failures). + nodeModulesPaths: [ + exampleNodeModules, + ], + // Force react and react-native to always resolve from the example app's + // node_modules. Without this, files under libraryRoot (e.g. src/) would + // pick up the library's devDependency versions which may differ from the + // versions the native binary was built against. + resolveRequest: (context, moduleName, platform) => { + if (forcedModules.includes(moduleName)) { + return context.resolveRequest( + { ...context, originModulePath: path.join(exampleNodeModules, 'react-native', 'dummy.js') }, + moduleName, + platform, + ); } - }) - } -} + return context.resolveRequest(context, moduleName, platform); + }, + }, +}; -module.exports = mergeConfig(getDefaultConfig(__dirname), config) +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/example/package-lock.json b/example/package-lock.json new file mode 100644 index 000000000..528e8a72f --- /dev/null +++ b/example/package-lock.json @@ -0,0 +1,12256 @@ +{ + "name": "BlePlxExample", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "BlePlxExample", + "version": "0.0.1", + "dependencies": { + "@react-native/new-app-screen": "0.84.1", + "@react-navigation/native": "^7.0.0", + "@react-navigation/native-stack": "^7.0.0", + "react": "19.2.3", + "react-native": "0.84.1", + "react-native-ble-plx": "file:../", + "react-native-safe-area-context": "^5.5.2", + "react-native-screens": "latest" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "20.1.0", + "@react-native-community/cli-platform-android": "20.1.0", + "@react-native-community/cli-platform-ios": "20.1.0", + "@react-native/babel-preset": "0.84.1", + "@react-native/eslint-config": "0.84.1", + "@react-native/metro-config": "0.84.1", + "@react-native/typescript-config": "0.84.1", + "@types/jest": "^29.5.13", + "@types/react": "^19.2.0", + "@types/react-test-renderer": "^19.1.0", + "eslint": "^8.19.0", + "jest": "^29.6.3", + "prettier": "2.8.8", + "react-test-renderer": "19.2.3", + "typescript": "^5.8.3" + }, + "engines": { + "node": ">= 22.11.0" + } + }, + "..": { + "version": "4.0.0-alpha.0", + "license": "MIT", + "devDependencies": { + "@babel/cli": "^7.25.9", + "@babel/core": "^7.25.2", + "@commitlint/config-conventional": "^17.0.2", + "@evilmartians/lefthook": "^1.5.0", + "@react-native-community/eslint-config": "^3.0.2", + "@react-native/eslint-config": "^0.73.1", + "@react-navigation/native": "^6.1.17", + "@react-navigation/native-stack": "^6.9.26", + "@release-it/conventional-changelog": "^5.0.0", + "@types/jest": "^29.5.5", + "@types/react": "^18.2.44", + "@types/react-native-base64": "^0.2.2", + "@typescript-eslint/parser": "^5.62.0", + "commitlint": "^17.0.2", + "del-cli": "^5.1.0", + "eslint": "^8.51.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-react-refresh": "^0.4.3", + "expo-module-scripts": "^3.1.0", + "jest": "^29.7.0", + "pod-install": "^0.1.0", + "prettier": "^3.0.3", + "react": "18.3.1", + "react-native": "0.77.0", + "react-native-base64": "^0.2.1", + "react-native-builder-bob": "^0.20.0", + "react-native-safe-area-context": "^4.10.5", + "react-native-toast-message": "^2.2.0", + "release-it": "^17.3.0", + "styled-components": "^6.1.11", + "turbo": "^1.10.7", + "typescript": "^5.2.2" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">= 0.82.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz", + "integrity": "sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@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/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.7.tgz", + "integrity": "sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz", + "integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz", + "integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", + "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@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/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@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/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^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" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "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": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.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.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "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 + } + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "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.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "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 + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@react-native-community/cli": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.1.0.tgz", + "integrity": "sha512-441WsVtRe4nGJ9OzA+QMU1+22lA6Q2hRWqqIMKD0wjEMLqcSfOZyu2UL9a/yRpL/dRpyUsU4n7AxqKfTKO/Csg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "20.1.0", + "@react-native-community/cli-config": "20.1.0", + "@react-native-community/cli-doctor": "20.1.0", + "@react-native-community/cli-server-api": "20.1.0", + "@react-native-community/cli-tools": "20.1.0", + "@react-native-community/cli-types": "20.1.0", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "picocolors": "^1.1.1", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "rnc-cli": "build/bin.js" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.1.0.tgz", + "integrity": "sha512-77L4DifWfxAT8ByHnkypge7GBMYpbJAjBGV+toowt5FQSGaTBDcBHCX+FFqFRukD5fH6i8sZ41Gtw+nbfCTTIA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.1.0", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.1.0.tgz", + "integrity": "sha512-1x9rhLLR/dKKb92Lb5O0l0EmUG08FHf+ZVyVEf9M+tX+p5QIm52MRiy43R0UAZ2jJnFApxRk+N3sxoYK4Dtnag==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.1.0", + "cosmiconfig": "^9.0.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.1", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-config-android": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.1.0.tgz", + "integrity": "sha512-3A01ZDyFeCALzzPcwP/fleHoP3sGNq1UX7FzxkTrOFX8RRL9ntXNXQd27E56VU4BBxGAjAJT4Utw8pcOjJceIA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.1.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.4.1", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-config-apple": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.1.0.tgz", + "integrity": "sha512-n6JVs8Q3yxRbtZQOy05ofeb1kGtspGN3SgwPmuaqvURF9fsuS7c4/9up2Kp9C+1D2J1remPJXiZLNGOcJvfpOA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.1.0", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.1.0.tgz", + "integrity": "sha512-QfJF1GVjA4PBrIT3SJ0vFFIu0km1vwOmLDlOYVqfojajZJ+Dnvl0f94GN1il/jT7fITAxom///XH3/URvi7YTQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config": "20.1.0", + "@react-native-community/cli-platform-android": "20.1.0", + "@react-native-community/cli-platform-apple": "20.1.0", + "@react-native-community/cli-platform-ios": "20.1.0", + "@react-native-community/cli-tools": "20.1.0", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.13.0", + "execa": "^5.0.0", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "picocolors": "^1.1.1", + "semver": "^7.5.2", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.1.0.tgz", + "integrity": "sha512-TeHPDThOwDppQRpndm9kCdRCBI8AMy3HSIQ+iy7VYQXL5BtZ5LfmGdusoj7nVN/ZGn0Lc6Gwts5qowyupXdeKg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config-android": "20.1.0", + "@react-native-community/cli-tools": "20.1.0", + "execa": "^5.0.0", + "logkitty": "^0.7.1", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-platform-apple": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.0.tgz", + "integrity": "sha512-0ih1hrYezSM2cuOlVnwBEFtMwtd8YgpTLmZauDJCv50rIumtkI1cQoOgLoS4tbPCj9U/Vn2a9BFH0DLFOOIacg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config-apple": "20.1.0", + "@react-native-community/cli-tools": "20.1.0", + "execa": "^5.0.0", + "fast-xml-parser": "^4.4.1", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.1.0.tgz", + "integrity": "sha512-XN7Da9z4WsJxtqVtEzY8q2bv22OsvzaFP5zy5+phMWNoJlU4lf7IvBSxqGYMpQ9XhYP7arDw5vmW4W34s06rnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-platform-apple": "20.1.0" + } + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.1.0.tgz", + "integrity": "sha512-Tb415Oh8syXNT2zOzLzFkBXznzGaqKCiaichxKzGCDKg6JGHp3jSuCmcTcaPeYC7oc32n/S3Psw7798r4Q/7lA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.1.0", + "body-parser": "^1.20.3", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "open": "^6.2.0", + "pretty-format": "^29.7.0", + "serve-static": "^1.13.1", + "ws": "^6.2.3" + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.0.tgz", + "integrity": "sha512-/YmzHGOkY6Bgrv4OaA1L8rFqsBlQd1EB2/ipAoKPiieV0EcB5PUamUSuNeFU3sBZZTYQCUENwX4wgOHgFUlDnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vscode/sudo-prompt": "^9.0.0", + "appdirsjs": "^1.2.4", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "launch-editor": "^2.9.1", + "mime": "^2.4.1", + "ora": "^5.4.1", + "picocolors": "^1.1.1", + "prompts": "^2.4.2", + "semver": "^7.5.2" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.1.0.tgz", + "integrity": "sha512-D0kDspcwgbVXyNjwicT7Bb1JgXjijTw1JJd+qxyF/a9+sHv7TU4IchV+gN38QegeXqVyM4Ym7YZIvXMFBmyJqA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.84.1.tgz", + "integrity": "sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", + "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.84.1" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", + "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.84.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", + "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.84.1.tgz", + "integrity": "sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.84.1", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.83.3", + "metro-config": "^0.83.3", + "metro-core": "^0.83.3", + "semver": "^7.1.3" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.84.1.tgz", + "integrity": "sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.84.1.tgz", + "integrity": "sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.84.1.tgz", + "integrity": "sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.84.1", + "@react-native/debugger-shell": "0.84.1", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native/eslint-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.84.1.tgz", + "integrity": "sha512-Z8j0uahXvPgvBAslxwIWwFliRQO0bL7nLpNiFroVnT1ojYCQHwFAbObDcAIs+Yva2FTat2A5m2rbi98MHVC+cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/eslint-parser": "^7.25.1", + "@react-native/eslint-plugin": "0.84.1", + "@typescript-eslint/eslint-plugin": "^8.36.0", + "@typescript-eslint/parser": "^8.36.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-ft-flow": "^2.0.1", + "eslint-plugin-jest": "^29.0.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-native": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0", + "prettier": ">=2" + } + }, + "node_modules/@react-native/eslint-plugin": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.84.1.tgz", + "integrity": "sha512-mKhsn3+CmN03vyW7/YQ6/LvLQppWT+eYqlCvmOvVoGlnh+XrJHJgwNr891zsyxGNELTwu/x2+T83ogwCmRHMEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.84.1.tgz", + "integrity": "sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", + "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", + "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.84.1", + "hermes-parser": "0.32.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", + "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "0.84.1", + "@react-native/metro-babel-transformer": "0.84.1", + "metro-config": "^0.83.3", + "metro-runtime": "^0.83.3" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/new-app-screen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/new-app-screen/-/new-app-screen-0.84.1.tgz", + "integrity": "sha512-O4DEXWwiXqTBVVUfbWYOjOAxzTm5wv5K/DDDz1HkuJIaVDbzeE8+hPb6kob4oig58XQS3ANZSc9KxBqbuQi/iA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@types/react": "^19.1.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.84.1.tgz", + "integrity": "sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==", + "license": "MIT" + }, + "node_modules/@react-native/typescript-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.84.1.tgz", + "integrity": "sha512-ar7Gn6ma3b+Ricdxn2sTZL2DT1NMlrfsWmOkFZegpfQJzheqX/8gzIB1aIbfZyvhEDsoz07RG7wmsyQAWqXjsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.84.1.tgz", + "integrity": "sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@react-navigation/core": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.16.1.tgz", + "integrity": "sha512-xhquoyhKdqDfiL7LuupbwYnmauUGfVFGDEJO34m26k8zSN1eDjQ2stBZcHN8ILOI1PrG9885nf8ZmfaQxPS0ww==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.5.3", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "query-string": "^7.1.3", + "react-is": "^19.1.0", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "react": ">= 18.2.0" + } + }, + "node_modules/@react-navigation/elements": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.10.tgz", + "integrity": "sha512-N8tuBekzTRb0pkMHFJGvmC6Q5OisSbt6gzvw7RHMnp4NDo5auVllT12sWFaTXf8mTduaLKNSrD/NZNaOqThCBg==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.1.33", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } + } + }, + "node_modules/@react-navigation/native": { + "version": "7.1.33", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.33.tgz", + "integrity": "sha512-DpFdWGcgLajKZ1TuIvDNQsblN2QaUFWpTQaB8v7WRP9Mix8H/6TFoIrZd93pbymI2hybd6UYrD+lI408eWVcfw==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.16.1", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" + } + }, + "node_modules/@react-navigation/native-stack": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.5.tgz", + "integrity": "sha512-NuyMf21kKk3jODvYgpcDA+HwyWr/KEj72ciqquyEupZlsmQ3WNUGgdaixEB3A19+iPOvHLQzDLcoTrrqZk8Leg==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.10", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.33", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/routers": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz", + "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-test-renderer": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", + "integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vscode/sudo-prompt": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", + "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-fragments/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.16.tgz", + "integrity": "sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.7", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.1.tgz", + "integrity": "sha512-ENp89vM9Pw4kv/koBb5N2f9bDZsR0hpf3BdPMOg/pkS3pwO4dzNnQZVXtBbeyAadgm865DmQG2jMMLqmZXvuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.7", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.7.tgz", + "integrity": "sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.7" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", + "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.32.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001779", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", + "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "devOptional": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "devOptional": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", + "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", + "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "engines": { + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint-plugin-ft-flow": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz", + "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "@babel/eslint-parser": "^7.12.0", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "29.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.0.tgz", + "integrity": "sha512-ZCGr7vTH2WSo2hrK5oM2RULFmMruQ7W3cX7YfwoTiPfzTGTFBMmrVIz45jZHd++cGKj/kWf02li/RhTGcANJSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.0.0" + }, + "engines": { + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <6.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/eslint-plugin-react-native": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-5.0.0.tgz", + "integrity": "sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-react-native-globals": "^0.1.1" + }, + "peerDependencies": { + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react-native-globals": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz", + "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.4.tgz", + "integrity": "sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-compiler": { + "version": "250829098.0.9", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.9.tgz", + "integrity": "sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==", + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "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 + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.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.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "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 + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "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.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "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 + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "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": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "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": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "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.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "devOptional": true, + "license": "BSD-3-Clause", + "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" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "devOptional": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/launch-editor": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.1.tgz", + "integrity": "sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } + }, + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/logkitty/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", + "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.33.3", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-config": "0.83.5", + "metro-core": "0.83.5", + "metro-file-map": "0.83.5", + "metro-resolver": "0.83.5", + "metro-runtime": "0.83.5", + "metro-source-map": "0.83.5", + "metro-symbolicate": "0.83.5", + "metro-transform-plugins": "0.83.5", + "metro-transform-worker": "0.83.5", + "mime-types": "^3.0.1", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", + "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.33.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", + "license": "MIT" + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.33.3" + } + }, + "node_modules/metro-cache": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", + "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", + "license": "MIT", + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.83.5" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-cache-key": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", + "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-config": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", + "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", + "license": "MIT", + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.83.5", + "metro-cache": "0.83.5", + "metro-core": "0.83.5", + "metro-runtime": "0.83.5", + "yaml": "^2.6.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-core": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", + "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.83.5" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-file-map": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", + "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-minify-terser": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", + "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-resolver": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", + "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-runtime": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", + "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-source-map": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", + "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.83.5", + "nullthrows": "^1.1.1", + "ob1": "0.83.5", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", + "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.83.5", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", + "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", + "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.83.5", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-minify-terser": "0.83.5", + "metro-source-map": "0.83.5", + "metro-transform-plugins": "0.83.5", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/metro/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.33.3" + } + }, + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/metro/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "devOptional": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "license": "MIT" + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", + "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, + "node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" + }, + "node_modules/react-native": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.84.1.tgz", + "integrity": "sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==", + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@react-native/assets-registry": "0.84.1", + "@react-native/codegen": "0.84.1", + "@react-native/community-cli-plugin": "0.84.1", + "@react-native/gradle-plugin": "0.84.1", + "@react-native/js-polyfills": "0.84.1", + "@react-native/normalize-colors": "0.84.1", + "@react-native/virtualized-lists": "0.84.1", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "base64-js": "^1.5.1", + "commander": "^12.0.0", + "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.9", + "invariant": "^2.2.4", + "jest-environment-node": "^29.7.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.83.3", + "metro-source-map": "^0.83.3", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.1.5", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.27.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@types/react": "^19.1.1", + "react": "^19.2.3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native-ble-plx": { + "resolved": "..", + "link": true + }, + "node_modules/react-native-safe-area-context": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz", + "integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.24.0.tgz", + "integrity": "sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-test-renderer": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.3.tgz", + "integrity": "sha512-TMR1LnSFiWZMJkCgNf5ATSvAheTT2NvKIwiVwdBPHxjBI7n/JbWd4gaZ16DVd9foAXdvDz+sB5yxZTwMjPRxpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^19.2.3", + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "devOptional": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "devOptional": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", + "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-latest-callback": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz", + "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/example/package.json b/example/package.json index b38581fc4..cc02001db 100644 --- a/example/package.json +++ b/example/package.json @@ -5,37 +5,41 @@ "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", + "lint": "eslint .", "start": "react-native start", - "pods": "pod-install --quiet" + "test": "jest" }, "dependencies": { - "@react-native-async-storage/async-storage": "^2.1.0", - "@react-navigation/native": "^7.0.14", - "@react-navigation/native-stack": "^7.2.0", - "react": "18.3.1", - "react-native": "0.77.0", - "react-native-base64": "^0.2.1", - "react-native-safe-area-context": "^5.1.0", - "react-native-screens": "^4.5.0", - "react-native-toast-message": "^2.1.6", - "styled-components": "^6.0.7" + "react": "19.2.3", + "react-native": "0.84.1", + "@react-native/new-app-screen": "0.84.1", + "@react-navigation/native": "^7.0.0", + "@react-navigation/native-stack": "^7.0.0", + "react-native-ble-plx": "file:../", + "react-native-safe-area-context": "^5.5.2", + "react-native-screens": "latest" }, "devDependencies": { "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "15.0.1", - "@react-native-community/cli-platform-android": "15.0.1", - "@react-native-community/cli-platform-ios": "15.0.1", - "@react-native/babel-preset": "0.77.0", - "@react-native/eslint-config": "0.77.0", - "@react-native/metro-config": "0.77.0", - "@react-native/typescript-config": "0.77.0", - "@types/react-native-base64": "^0.2.0", - "babel-plugin-module-resolver": "^5.0.0", - "metro-react-native-babel-preset": "0.76.8" + "@react-native-community/cli": "20.1.0", + "@react-native-community/cli-platform-android": "20.1.0", + "@react-native-community/cli-platform-ios": "20.1.0", + "@react-native/babel-preset": "0.84.1", + "@react-native/eslint-config": "0.84.1", + "@react-native/metro-config": "0.84.1", + "@react-native/typescript-config": "0.84.1", + "@types/jest": "^29.5.13", + "@types/react": "^19.2.0", + "@types/react-test-renderer": "^19.1.0", + "eslint": "^8.19.0", + "jest": "^29.6.3", + "prettier": "2.8.8", + "react-test-renderer": "19.2.3", + "typescript": "^5.8.3" }, "engines": { - "node": ">=18" + "node": ">= 22.11.0" } -} +} \ No newline at end of file diff --git a/example/react-native.config.js b/example/react-native.config.js deleted file mode 100644 index ef5b6b646..000000000 --- a/example/react-native.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const path = require('path') -const pak = require('../package.json') - -module.exports = { - dependencies: { - [pak.name]: { - root: path.join(__dirname, '..') - } - } -} diff --git a/example/src/App.tsx b/example/src/App.tsx index ea87f8838..2815767ef 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,17 +1,77 @@ -import React from 'react' -import { SafeAreaProvider } from 'react-native-safe-area-context' -import { ThemeProvider } from 'styled-components' -import Toast from 'react-native-toast-message' -import { commonTheme } from './theme/theme' -import { Navigation } from './navigation' +import React, { useEffect, useRef } from 'react' +import { NavigationContainer } from '@react-navigation/native' +import { createNativeStackNavigator } from '@react-navigation/native-stack' +import { BleManager } from 'react-native-ble-plx' + +import ScanScreen from './screens/ScanScreen' +import DeviceScreen from './screens/DeviceScreen' +import CharacteristicScreen from './screens/CharacteristicScreen' +import L2CAPScreen from './screens/L2CAPScreen' + +export type RootStackParamList = { + Scan: { manager: BleManager } + Device: { manager: BleManager; deviceId: string; deviceName: string | null } + Characteristic: { + manager: BleManager + deviceId: string + serviceUuid: string + characteristicUuid: string + properties: { + isReadable: boolean + isWritableWithResponse: boolean + isWritableWithoutResponse: boolean + isNotifying: boolean + isIndicatable: boolean + } + } + L2CAP: { + manager: BleManager + deviceId: string + psm: number + } +} + +const Stack = createNativeStackNavigator() + +export default function App() { + const managerRef = useRef(new BleManager()) + + useEffect(() => { + const manager = managerRef.current + manager.createClient().catch((e: any) => { + console.warn('Failed to create BLE client:', e) + }) + + return () => { + manager.destroyClient().catch(() => {}) + } + }, []) -export function App() { return ( - - - - - - + + + + + + + + ) } diff --git a/example/src/components/atoms/AppText/AppText.styled.tsx b/example/src/components/atoms/AppText/AppText.styled.tsx deleted file mode 100644 index 864f4600d..000000000 --- a/example/src/components/atoms/AppText/AppText.styled.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Text } from 'react-native' -import styled, { css } from 'styled-components' - -export const StyledText = styled(Text)` - ${({ theme }) => css` - font-size: ${theme.sizes.defaultFontSize}px; - font-weight: 800; - `} -` diff --git a/example/src/components/atoms/AppText/AppText.tsx b/example/src/components/atoms/AppText/AppText.tsx deleted file mode 100644 index 4d0dc5c38..000000000 --- a/example/src/components/atoms/AppText/AppText.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import type { TextProps } from 'react-native' -import { StyledText } from './AppText.styled' - -export function AppText(props: TextProps) { - return -} diff --git a/example/src/components/atoms/AppTextInput/AppTextInput.styled.tsx b/example/src/components/atoms/AppTextInput/AppTextInput.styled.tsx deleted file mode 100644 index a1b5419ff..000000000 --- a/example/src/components/atoms/AppTextInput/AppTextInput.styled.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { TextInput } from 'react-native' -import styled, { css } from 'styled-components' - -export const StyledTextInput = styled(TextInput)` - ${({ theme }) => css` - font-size: ${theme.sizes.defaultFontSize}px; - font-weight: 800; - border-radius: 100px; - border-color: ${theme.colors.mainRed}; - border-width: 1px; - padding: 0px 24px; - height: 50px; - margin-bottom: 12px; - `} -` diff --git a/example/src/components/atoms/AppTextInput/AppTextInput.tsx b/example/src/components/atoms/AppTextInput/AppTextInput.tsx deleted file mode 100644 index 0e00a1653..000000000 --- a/example/src/components/atoms/AppTextInput/AppTextInput.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import type { TextInputProps } from 'react-native' -import { StyledTextInput } from './AppTextInput.styled' - -export function AppTextInput(props: TextInputProps) { - return -} diff --git a/example/src/components/atoms/Button/Button.styled.tsx b/example/src/components/atoms/Button/Button.styled.tsx deleted file mode 100644 index 414a4eda2..000000000 --- a/example/src/components/atoms/Button/Button.styled.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { TouchableOpacity } from 'react-native' -import styled, { css } from 'styled-components' -import { AppText } from '../AppText/AppText' - -export const Container = styled(TouchableOpacity)` - ${({ theme }) => css` - background-color: ${theme.colors.mainRed}; - margin: 5px 0px; - padding: 12px; - align-items: center; - border-radius: 100px; - `} -` - -export const StyledText = styled(AppText)` - color: white; -` diff --git a/example/src/components/atoms/Button/Button.tsx b/example/src/components/atoms/Button/Button.tsx deleted file mode 100644 index 9a2984bfe..000000000 --- a/example/src/components/atoms/Button/Button.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import type { TouchableOpacityProps } from 'react-native' -import { Container, StyledText } from './Button.styled' - -export type AppButtonProps = TouchableOpacityProps & { - label: string -} - -export function AppButton({ label, ...props }: AppButtonProps) { - return ( - - {label} - - ) -} diff --git a/example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx b/example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx deleted file mode 100644 index c0e6ac126..000000000 --- a/example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SafeAreaView } from 'react-native-safe-area-context' -import styled, { css } from 'styled-components' - -export const Container = styled(SafeAreaView)` - ${({ theme }) => css` - flex: 1; - padding: ${theme.sizes.defaultScreenPadding}px; - `} -` diff --git a/example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx b/example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx deleted file mode 100644 index dd06f9c1d..000000000 --- a/example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { Container } from './ScreenDefaultContainer.styled' - -export type ScreenDefaultContainerProps = { - children: React.ReactNode -} - -export function ScreenDefaultContainer({ children }: ScreenDefaultContainerProps) { - return {children} -} diff --git a/example/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx b/example/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx deleted file mode 100644 index cc2db6bc8..000000000 --- a/example/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { View } from 'react-native' -import styled, { css } from 'styled-components' -import { AppText } from '../AppText/AppText' - -export const Container = styled(View)` - ${({ theme }) => css` - border-bottom-width: 1px; - border-bottom-color: ${theme.colors.mainRed}; - padding-bottom: 5px; - margin: 10px 0px; - `} -` - -export const Header = styled(View)` - flex-direction: row; - justify-content: space-between; -` - -export const Label = styled(AppText)` - flex: 1; - margin-right: 12px; -` diff --git a/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx b/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx deleted file mode 100644 index 293665143..000000000 --- a/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import type { TestStateType } from '../../../types' -import { AppText } from '../AppText/AppText' -import { Container, Header, Label } from './TestStateDisplay.styled' - -export type TestStateDisplayProps = { - label?: string - state?: TestStateType - value?: string -} - -const marks: Record = { - DONE: '\u2705', - ERROR: '\u274C', - WAITING: '\u231B', - IN_PROGRESS: '\u260E' -} - -export function TestStateDisplay({ label, state, value }: TestStateDisplayProps) { - return ( - -
- - {!!state && {marks[state]}} -
- {!!value && {value}} -
- ) -} diff --git a/example/src/components/atoms/index.ts b/example/src/components/atoms/index.ts deleted file mode 100644 index 3f14cec6a..000000000 --- a/example/src/components/atoms/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Button/Button' -export * from './AppText/AppText' -export * from './ScreenDefaultContainer/ScreenDefaultContainer' -export * from './TestStateDisplay/TestStateDisplay' -export * from './AppTextInput/AppTextInput' diff --git a/example/src/components/molecules/BleDevice/BleDevice.styled.tsx b/example/src/components/molecules/BleDevice/BleDevice.styled.tsx deleted file mode 100644 index d650a1557..000000000 --- a/example/src/components/molecules/BleDevice/BleDevice.styled.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { TouchableOpacity } from 'react-native' -import styled, { css } from 'styled-components' - -export const Container = styled(TouchableOpacity)` - ${({ theme }) => css` - border-color: ${theme.colors.mainRed}; - border-width: 1px; - padding: 12px; - border-radius: 12px; - margin-top: 12px; - `} -` diff --git a/example/src/components/molecules/BleDevice/BleDevice.tsx b/example/src/components/molecules/BleDevice/BleDevice.tsx deleted file mode 100644 index a1084771c..000000000 --- a/example/src/components/molecules/BleDevice/BleDevice.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import { Device } from 'react-native-ble-plx' -import { Container } from './BleDevice.styled' -import { DeviceProperty } from './DeviceProperty/DeviceProperty' - -export type BleDeviceProps = { - onPress: (device: Device) => void - device: Device -} - -export function BleDevice({ device, onPress }: BleDeviceProps) { - const isConnectableInfoValueIsUnavailable = typeof device.isConnectable !== 'boolean' - const isConnectableValue = device.isConnectable ? 'true' : 'false' - const parsedIsConnectable = isConnectableInfoValueIsUnavailable ? '-' : isConnectableValue - - return ( - onPress(device)}> - - - - - - - - - - ) -} diff --git a/example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx b/example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx deleted file mode 100644 index c96d169d0..000000000 --- a/example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View } from 'react-native' -import styled from 'styled-components' -import { AppText } from '../../../atoms' - -export const Container = styled(View)` - flex-direction: row; - flex-wrap: wrap; -` - -export const StyledTitleText = styled(AppText)` - font-weight: 800; -` - -export const StyledValueText = styled(AppText)` - font-weight: 500; -` diff --git a/example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx b/example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx deleted file mode 100644 index 867904dc8..000000000 --- a/example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { Container, StyledTitleText, StyledValueText } from './DeviceProperty.styled' - -export type DevicePropertyProps = { - name: string - value?: number | string | null -} - -export function DeviceProperty({ name, value }: DevicePropertyProps) { - return ( - - {name}: - {value || '-'} - - ) -} diff --git a/example/src/components/molecules/index.ts b/example/src/components/molecules/index.ts deleted file mode 100644 index 39d6d9ff1..000000000 --- a/example/src/components/molecules/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BleDevice/BleDevice' diff --git a/example/src/consts/nRFDeviceConsts.ts b/example/src/consts/nRFDeviceConsts.ts deleted file mode 100644 index 66395ca69..000000000 --- a/example/src/consts/nRFDeviceConsts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { fullUUID } from 'react-native-ble-plx' -import base64 from 'react-native-base64' -import { getDateAsBase64 } from '../utils/getDateAsBase64' - -export const deviceTimeService = fullUUID('1847') -export const currentTimeCharacteristic = fullUUID('2A2B') -export const deviceTimeCharacteristic = fullUUID('2B90') -export const currentTimeCharacteristicTimeTriggerDescriptor = fullUUID('290E') - -export const writeWithResponseBase64Time = getDateAsBase64(new Date('2022-08-11T08:17:19Z')) -export const writeWithoutResponseBase64Time = getDateAsBase64(new Date('2023-09-12T10:12:16Z')) -export const monitorExpectedMessage = 'Hi, it works!' -export const currentTimeCharacteristicTimeTriggerDescriptorValue = base64.encode('BLE-PLX') diff --git a/example/src/navigation/components/commonScreenOptions.tsx b/example/src/navigation/components/commonScreenOptions.tsx deleted file mode 100644 index 0c412e25d..000000000 --- a/example/src/navigation/components/commonScreenOptions.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { NativeStackNavigationOptions } from '@react-navigation/native-stack' -import { useTheme } from 'styled-components' - -export const useCommonScreenOptions: () => NativeStackNavigationOptions = () => { - const theme = useTheme() - - return { - headerShadowVisible: false, - headerTitleStyle: { - fontSize: 22 - }, - headerTitleAlign: 'center', - headerBackTitleVisible: false, - orientation: 'portrait', - title: '', - headerTintColor: 'white', - headerStyle: { - backgroundColor: theme.colors.mainRed - } - } -} diff --git a/example/src/navigation/components/index.ts b/example/src/navigation/components/index.ts deleted file mode 100644 index 2df86427d..000000000 --- a/example/src/navigation/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './commonScreenOptions' diff --git a/example/src/navigation/index.ts b/example/src/navigation/index.ts deleted file mode 100644 index 9c8479a49..000000000 --- a/example/src/navigation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './navigation' diff --git a/example/src/navigation/navigation.tsx b/example/src/navigation/navigation.tsx deleted file mode 100644 index 28f689f42..000000000 --- a/example/src/navigation/navigation.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import { NavigationContainer, DefaultTheme } from '@react-navigation/native' - -import { MainStackComponent, type MainStackParamList } from './navigators' - -const mainTheme = { - ...DefaultTheme, - dark: false, - colors: { - ...DefaultTheme.colors, - card: 'white', - background: 'white' - } -} - -export type AllScreenTypes = MainStackParamList - -// eslint-disable-next-line prettier/prettier -declare global { - namespace ReactNavigation { - interface RootParamList extends AllScreenTypes {} - } -} - -export function Navigation() { - return ( - - - - ) -} diff --git a/example/src/navigation/navigators/MainStack.tsx b/example/src/navigation/navigators/MainStack.tsx deleted file mode 100644 index d10986d6b..000000000 --- a/example/src/navigation/navigators/MainStack.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react' -import { createNativeStackNavigator } from '@react-navigation/native-stack' -import * as screenComponents from '../../screens' -import { useCommonScreenOptions } from '../components' - -export type MainStackParamList = { - DASHBOARD_SCREEN: undefined - DEVICE_DETAILS_SCREEN: undefined - DEVICE_NRF_TEST_SCREEN: undefined - DEVICE_CONNECT_DISCONNECT_TEST_SCREEN: undefined - AUTODISCONNECT_SCREEN: undefined - INSTANCE_DESTROY_SCREEN: undefined - DEVICE_ON_DISCONNECT_TEST_SCREEN: undefined -} - -const MainStack = createNativeStackNavigator() - -export function MainStackComponent() { - const commonScreenOptions = useCommonScreenOptions() - - return ( - - - - - - - - - ) -} diff --git a/example/src/navigation/navigators/index.ts b/example/src/navigation/navigators/index.ts deleted file mode 100644 index 472fafd1e..000000000 --- a/example/src/navigation/navigators/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './MainStack' diff --git a/example/src/screens/CharacteristicScreen.tsx b/example/src/screens/CharacteristicScreen.tsx new file mode 100644 index 000000000..348601118 --- /dev/null +++ b/example/src/screens/CharacteristicScreen.tsx @@ -0,0 +1,272 @@ +import React, { useState, useCallback, useRef, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + TextInput, + Switch, + StyleSheet, + Alert, + ScrollView, + Platform, +} from 'react-native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { Subscription } from 'react-native-ble-plx'; +import type { RootStackParamList } from '../App'; + +type Props = NativeStackScreenProps; + +function base64ByteLength(b64: string): number { + const padding = (b64.match(/=+$/) || [''])[0].length; + return Math.floor((b64.length * 3) / 4) - padding; +} + +export default function CharacteristicScreen({ route }: Props) { + const { manager, deviceId, serviceUuid, characteristicUuid, properties } = + route.params; + + const [value, setValue] = useState('(none)'); + const [writeValue, setWriteValue] = useState(''); + const [monitoring, setMonitoring] = useState(false); + const [sampleCount, setSampleCount] = useState(0); + const [distinctCount, setDistinctCount] = useState(0); + const [stoppedAtCount, setStoppedAtCount] = useState(null); + const [writeError, setWriteError] = useState(null); + const [payloadLength, setPayloadLength] = useState(null); + const monitorSub = useRef(null); + const sampleCountRef = useRef(0); + const seenValues = useRef>(new Set()); + + useEffect(() => { + return () => { + monitorSub.current?.remove(); + }; + }, []); + + const readCharacteristic = useCallback(async () => { + try { + const result = await manager.readCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + ); + const readValue = result.value ?? '(null)'; + setValue(readValue); + if (result.value) { + setPayloadLength(base64ByteLength(result.value)); + } + } catch (e: any) { + Alert.alert('Read Error', e.message || String(e)); + } + }, [manager, deviceId, serviceUuid, characteristicUuid]); + + const writeCharacteristic = useCallback(async () => { + try { + await manager.writeCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + writeValue, + properties.isWritableWithResponse, + ); + setWriteError(null); + Alert.alert('Write', 'Value written successfully'); + } catch (e: any) { + setWriteError(e.message || String(e)); + Alert.alert('Write Error', e.message || String(e)); + } + }, [manager, deviceId, serviceUuid, characteristicUuid, writeValue, properties]); + + const toggleMonitor = useCallback( + (enabled: boolean) => { + if (enabled) { + // Reset counters + setSampleCount(0); + setDistinctCount(0); + setStoppedAtCount(null); + sampleCountRef.current = 0; + seenValues.current = new Set(); + + monitorSub.current = manager.monitorCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + (error: any, event: any) => { + if (error) { + console.warn('Monitor error:', error); + setMonitoring(false); + return; + } + if (event) { + setValue(event.value); + sampleCountRef.current += 1; + setSampleCount((c) => c + 1); + if (!seenValues.current.has(event.value)) { + seenValues.current.add(event.value); + setDistinctCount((c) => c + 1); + } + } + }, + ); + setMonitoring(true); + } else { + monitorSub.current?.remove(); + monitorSub.current = null; + setStoppedAtCount(sampleCountRef.current); + setMonitoring(false); + } + }, + [manager, deviceId, serviceUuid, characteristicUuid], + ); + + return ( + + Characteristic + {characteristicUuid} + Service: {serviceUuid} + + + Properties: + + {[ + properties.isReadable && 'Readable', + properties.isWritableWithResponse && 'Writable (response)', + properties.isWritableWithoutResponse && 'Writable (no response)', + properties.isNotifying && 'Notifiable', + properties.isIndicatable && 'Indicatable', + ] + .filter(Boolean) + .join(', ') || 'None'} + + + + + + Value: {value} + + + + {payloadLength != null && ( + + Payload: {payloadLength} bytes + + )} + + {/* E2E instrumentation: sample and distinct counts for monitor assertions */} + {(properties.isNotifying || properties.isIndicatable) && ( + + + Samples: {sampleCount} + + + Distinct: {distinctCount} + + + )} + + {stoppedAtCount != null && ( + + {sampleCount <= stoppedAtCount + 1 ? 'Monitor: STOPPED' : 'Monitor: LEAKED'} + + )} + + {properties.isReadable && ( + + Read + + )} + + {(properties.isWritableWithResponse || + properties.isWritableWithoutResponse) && ( + + + + setWriteValue('QQ==')}> + 1 byte + + setWriteValue('QUJDREVGR0hJSktMTU5PUFFSU1Q=')}> + 20 bytes + + + + Write + + {writeError && ( + + {writeError} + + )} + + )} + + {(properties.isNotifying || properties.isIndicatable) && ( + + Monitor + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#fff' }, + content: { padding: 16 }, + title: { fontSize: 20, fontWeight: '700' }, + uuid: { fontSize: 13, color: '#888', marginTop: 4 }, + service: { fontSize: 12, color: '#aaa', marginTop: 2 }, + propsRow: { marginTop: 12 }, + propLabel: { fontSize: 14, fontWeight: '600' }, + propValue: { fontSize: 13, color: '#555', marginTop: 2 }, + section: { marginTop: 16, marginBottom: 12 }, + valueText: { fontSize: 16, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace' }, + countersRow: { flexDirection: 'row', gap: 16, marginBottom: 12 }, + counterText: { fontSize: 13, color: '#666', fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace' }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + alignSelf: 'flex-start', + marginTop: 8, + }, + buttonText: { color: '#fff', fontWeight: '600' }, + writeSection: { marginTop: 12 }, + textInput: { + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + fontSize: 14, + }, + presetRow: { flexDirection: 'row', gap: 8, marginTop: 8 }, + presetButton: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6, borderWidth: 1, borderColor: '#007AFF', backgroundColor: '#E8F0FE' }, + presetText: { fontSize: 12, color: '#007AFF', fontWeight: '600' }, + monitorRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 16, + paddingVertical: 8, + }, + monitorLabel: { fontSize: 16, fontWeight: '600' }, + errorText: { fontSize: 13, color: '#FF3B30', marginTop: 8 }, +}); diff --git a/example/src/screens/DeviceScreen.tsx b/example/src/screens/DeviceScreen.tsx new file mode 100644 index 000000000..9b95c1081 --- /dev/null +++ b/example/src/screens/DeviceScreen.tsx @@ -0,0 +1,316 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + FlatList, + StyleSheet, + Alert, + TextInput, + Platform, +} from 'react-native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { RootStackParamList } from '../App'; + +type Props = NativeStackScreenProps; + +// Known test peripheral characteristics for quick access +const TEST_SERVICE_UUID = '12345678-1234-1234-1234-123456789ABC'; +const TEST_CHARACTERISTICS: { + uuid: string; + testID: string; + label: string; + props: { + isReadable: boolean; + isWritableWithResponse: boolean; + isWritableWithoutResponse: boolean; + isNotifying: boolean; + isIndicatable: boolean; + }; +}[] = [ + { uuid: '12345678-1234-1234-1234-123456789A01', testID: 'char-read-counter', label: 'Read Counter', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A02', testID: 'char-write-echo', label: 'Write Echo', props: { isReadable: true, isWritableWithResponse: true, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A03', testID: 'char-notify-stream', label: 'Notify Stream', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: true, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A04', testID: 'char-indicate-stream', label: 'Indicate Stream', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: true } }, + { uuid: '12345678-1234-1234-1234-123456789A05', testID: 'char-mtu-test', label: 'MTU Test', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A06', testID: 'char-write-no-response', label: 'Write No Response', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: true, isNotifying: false, isIndicatable: false } }, + { uuid: '12345678-1234-1234-1234-123456789A07', testID: 'char-l2cap-psm', label: 'L2CAP PSM', props: { isReadable: true, isWritableWithResponse: false, isWritableWithoutResponse: false, isNotifying: false, isIndicatable: false } }, +]; + +export default function DeviceScreen({ navigation, route }: Props) { + const { manager, deviceId, deviceName } = route.params; + const [mtu, setMtu] = useState(null); + const [serviceUuids, setServiceUuids] = useState([]); + const [discovering, setDiscovering] = useState(false); + const [customCharUuid, setCustomCharUuid] = useState(''); + const [selectedService, setSelectedService] = useState(null); + const [mtuRequestStatus, setMtuRequestStatus] = useState(null); + + useEffect(() => { + manager.getMtu(deviceId).then(setMtu).catch(() => {}); + }, [manager, deviceId]); + + const discoverServices = useCallback(async () => { + setDiscovering(true); + try { + const info = await manager.discoverAllServicesAndCharacteristics(deviceId); + const uuids = (info.serviceUuids as string[]) || []; + setServiceUuids(uuids); + // Auto-expand the test service if found + const testSvc = uuids.find((u) => u.toUpperCase() === TEST_SERVICE_UUID); + if (testSvc) { + setSelectedService(testSvc); + } + } catch (e: any) { + Alert.alert('Discovery Error', e.message || String(e)); + } finally { + setDiscovering(false); + } + }, [manager, deviceId]); + + const requestMtu = useCallback(async () => { + try { + await manager.requestMTUForDevice(deviceId, 247); + const newMtu = await manager.getMtu(deviceId); + setMtu(newMtu); + setMtuRequestStatus(`Success: MTU is now ${newMtu}`); + } catch (e: any) { + setMtuRequestStatus(`Error: ${e.message || String(e)}`); + } + }, [manager, deviceId]); + + const disconnect = useCallback(async () => { + try { + await manager.cancelDeviceConnection(deviceId); + navigation.goBack(); + } catch (e: any) { + Alert.alert('Disconnect Error', e.message || String(e)); + } + }, [manager, deviceId, navigation]); + + const navigateToCharacteristic = useCallback( + (serviceUuid: string, charUuid: string, props: { + isReadable: boolean; + isWritableWithResponse: boolean; + isWritableWithoutResponse: boolean; + isNotifying: boolean; + isIndicatable: boolean; + }) => { + navigation.navigate('Characteristic', { + manager, + deviceId, + serviceUuid, + characteristicUuid: charUuid, + properties: props, + }); + }, + [manager, deviceId, navigation], + ); + + const L2CAP_PSM_UUID = '12345678-1234-1234-1234-123456789A07'; + + const openL2CAP = useCallback(async (serviceUuid: string) => { + try { + const result = await manager.readCharacteristicForDevice( + deviceId, serviceUuid, L2CAP_PSM_UUID, + ); + if (!result.value) { + Alert.alert('L2CAP Error', 'PSM characteristic returned no value'); + return; + } + // Decode base64 uint16 LE — use simple lookup table + const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + const raw = result.value.replace(/=/g, ''); + const decoded: number[] = []; + for (let i = 0; i < raw.length; i += 4) { + const a = b64.indexOf(raw[i]); + const b = b64.indexOf(raw[i + 1] || 'A'); + const c = b64.indexOf(raw[i + 2] || 'A'); + const d = b64.indexOf(raw[i + 3] || 'A'); + decoded.push((a << 2) | (b >> 4)); + if (raw[i + 2]) decoded.push(((b & 15) << 4) | (c >> 2)); + if (raw[i + 3]) decoded.push(((c & 3) << 6) | d); + } + const psm = decoded[0] | ((decoded[1] || 0) << 8); + if (psm === 0) { + Alert.alert('L2CAP Error', 'PSM is 0 — L2CAP server not ready'); + return; + } + navigation.navigate('L2CAP', { manager, deviceId, psm }); + } catch (e: any) { + Alert.alert('L2CAP Error', e.message || String(e)); + } + }, [manager, deviceId, navigation]); + + const isTestService = (uuid: string) => + uuid.toUpperCase() === TEST_SERVICE_UUID; + + return ( + + + {deviceName || 'Unknown Device'} + ID: {deviceId} + {mtu != null && MTU: {mtu}} + + + + + + {discovering ? 'Discovering...' : 'Discover Services'} + + + + + Disconnect + + + + {Platform.OS === 'android' && ( + + + Request MTU 247 + + {mtuRequestStatus != null && ( + + {mtuRequestStatus} + + )} + + )} + + {serviceUuids.length > 0 && ( + item} + renderItem={({ item: svcUuid }) => ( + + setSelectedService(selectedService === svcUuid ? null : svcUuid)} + style={styles.serviceHeader}> + + {selectedService === svcUuid ? '▼' : '▶'} Service: {svcUuid} + + {isTestService(svcUuid) && ( + TEST + )} + + + {selectedService === svcUuid && isTestService(svcUuid) && ( + + {TEST_CHARACTERISTICS.map(({ uuid: charUuid, testID, label, props }) => ( + charUuid.toUpperCase().endsWith('9A07') + ? openL2CAP(svcUuid) + : navigateToCharacteristic(svcUuid, charUuid, props)}> + {label} + {charUuid} + + {[ + props.isReadable && 'Read', + props.isWritableWithResponse && 'Write', + props.isNotifying && 'Notify', + props.isIndicatable && 'Indicate', + ] + .filter(Boolean) + .join(', ')} + + + ))} + + )} + + {selectedService === svcUuid && !isTestService(svcUuid) && ( + + + Enter a characteristic UUID to interact with: + + + + navigateToCharacteristic(svcUuid, customCharUuid, { + isReadable: true, + isWritableWithResponse: true, + isWritableWithoutResponse: false, + isNotifying: true, + isIndicatable: true, + }) + }> + Open + + + )} + + )} + /> + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, backgroundColor: '#fff' }, + infoSection: { marginBottom: 16 }, + deviceName: { fontSize: 20, fontWeight: '700' }, + deviceId: { fontSize: 13, color: '#888', marginTop: 4 }, + mtuText: { fontSize: 13, color: '#888', marginTop: 2 }, + buttonRow: { flexDirection: 'row', gap: 12, marginBottom: 16 }, + mtuRequestSection: { marginBottom: 16 }, + mtuStatusText: { fontSize: 13, color: '#333', marginTop: 8 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + buttonDisabled: { backgroundColor: '#ccc' }, + disconnectButton: { backgroundColor: '#FF3B30' }, + buttonText: { color: '#fff', fontWeight: '600' }, + serviceGroup: { marginBottom: 12, borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, overflow: 'hidden' }, + serviceHeader: { flexDirection: 'row', alignItems: 'center', padding: 12, backgroundColor: '#f8f8f8' }, + serviceUuid: { fontSize: 13, fontWeight: '600', flex: 1 }, + testBadge: { fontSize: 10, fontWeight: '700', color: '#007AFF', backgroundColor: '#E8F0FE', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, overflow: 'hidden' }, + charList: { padding: 12 }, + charRow: { + paddingVertical: 10, + paddingHorizontal: 8, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + charLabel: { fontSize: 14, fontWeight: '600' }, + charUuid: { fontSize: 11, color: '#888', marginTop: 2 }, + charProps: { fontSize: 11, color: '#007AFF', marginTop: 2 }, + unknownNote: { fontSize: 13, color: '#666', marginBottom: 8 }, + textInput: { + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + fontSize: 14, + marginBottom: 8, + }, +}); diff --git a/example/src/screens/L2CAPScreen.tsx b/example/src/screens/L2CAPScreen.tsx new file mode 100644 index 000000000..a35d6e98f --- /dev/null +++ b/example/src/screens/L2CAPScreen.tsx @@ -0,0 +1,184 @@ +import React, { useState, useCallback, useRef, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + TextInput, + StyleSheet, + Alert, + ScrollView, + Platform, +} from 'react-native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { RootStackParamList } from '../App'; + +type Props = NativeStackScreenProps; + +export default function L2CAPScreen({ route }: Props) { + const { manager, deviceId, psm } = route.params; + const [channelId, setChannelId] = useState(null); + const [writeData, setWriteData] = useState(''); + const [status, setStatus] = useState('Not connected'); + const [lastError, setLastError] = useState(null); + const [rxData, setRxData] = useState(null); + const channelIdRef = useRef(null); + const rxSub = useRef(null); + + useEffect(() => { + return () => { + rxSub.current?.remove(); + rxSub.current = null; + if (channelIdRef.current != null) { + manager.closeL2CAPChannel(channelIdRef.current).catch(() => {}); + } + }; + }, [manager]); + + const openChannel = useCallback(async () => { + try { + setStatus('Opening...'); + setLastError(null); + const result = await manager.openL2CAPChannel(deviceId, psm); + setChannelId(result.channelId); + channelIdRef.current = result.channelId; + setStatus(`Connected (channel ${result.channelId})`); + rxSub.current = manager.monitorL2CAPChannel(result.channelId, (_error: any, data: any) => { + if (data?.data) { + setRxData(data.data); + } + }); + } catch (e: any) { + setLastError(e.message || String(e)); + setStatus('Failed'); + Alert.alert('L2CAP Error', e.message || String(e)); + } + }, [manager, deviceId, psm]); + + const writeChannel = useCallback(async () => { + if (channelId == null) return; + try { + setLastError(null); + await manager.writeL2CAPChannel(channelId, writeData); + setStatus(`Wrote ${writeData.length} chars`); + } catch (e: any) { + setLastError(e.message || String(e)); + Alert.alert('Write Error', e.message || String(e)); + } + }, [manager, channelId, writeData]); + + const closeChannel = useCallback(async () => { + if (channelId == null) return; + const closingId = channelId; + channelIdRef.current = null; // Prevent double-close from useEffect + setChannelId(null); + try { + await manager.closeL2CAPChannel(closingId); + rxSub.current?.remove(); + rxSub.current = null; + setRxData(null); + setStatus('Closed'); + } catch (e: any) { + // Channel close failed — still clean up subscription + rxSub.current?.remove(); + rxSub.current = null; + setLastError(e.message || String(e)); + setStatus('Close failed'); + } + }, [manager, channelId]); + + return ( + + L2CAP Channel + PSM: {psm} + Device: {deviceId} + + + Status: {status} + + + {lastError && ( + + Error: {lastError} + + )} + + {channelId == null ? ( + + Open Channel + + ) : ( + <> + + Channel ID: {channelId} + + + + + + Write + + + + {rxData != null && ( + + Received: {rxData} + + )} + + + Close Channel + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#fff' }, + content: { padding: 16 }, + title: { fontSize: 20, fontWeight: '700' }, + info: { fontSize: 13, color: '#888', marginTop: 4 }, + statusText: { + fontSize: 16, + fontWeight: '600', + marginTop: 16, + marginBottom: 12, + fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + }, + errorText: { fontSize: 13, color: '#FF3B30', marginBottom: 8 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + alignSelf: 'flex-start', + marginTop: 8, + }, + closeButton: { backgroundColor: '#FF3B30' }, + buttonText: { color: '#fff', fontWeight: '600' }, + writeSection: { marginTop: 12 }, + textInput: { + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + fontSize: 14, + marginBottom: 8, + }, +}); diff --git a/example/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx b/example/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx deleted file mode 100644 index 375a05507..000000000 --- a/example/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FlatList, View } from 'react-native' -import styled from 'styled-components' - -export const DropDown = styled(View)` - z-index: 100; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: #00000066; - align-items: center; - justify-content: center; -` - -export const DevicesList = styled(FlatList)` - flex: 1; -` diff --git a/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx b/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx deleted file mode 100644 index 229fe96ca..000000000 --- a/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { FlatList } from 'react-native' -import { Device } from 'react-native-ble-plx' -import { AppButton, AppText, ScreenDefaultContainer } from '../../../components/atoms' -import type { MainStackParamList } from '../../../navigation/navigators' -import { BLEService } from '../../../services' -import { BleDevice } from '../../../components/molecules' -import { cloneDeep } from '../../../utils/cloneDeep' -import { DropDown } from './DashboardScreen.styled' - -type DashboardScreenProps = NativeStackScreenProps -type DeviceExtendedByUpdateTime = Device & { updateTimestamp: number } - -const MIN_TIME_BEFORE_UPDATE_IN_MILLISECONDS = 5000 - -export function DashboardScreen({ navigation }: DashboardScreenProps) { - const [isConnecting, setIsConnecting] = useState(false) - const [foundDevices, setFoundDevices] = useState([]) - - const addFoundDevice = (device: Device) => - setFoundDevices(prevState => { - if (!isFoundDeviceUpdateNecessary(prevState, device)) { - return prevState - } - // deep clone - const nextState = cloneDeep(prevState) - const extendedDevice: DeviceExtendedByUpdateTime = { - ...device, - updateTimestamp: Date.now() + MIN_TIME_BEFORE_UPDATE_IN_MILLISECONDS - } as DeviceExtendedByUpdateTime - - const indexToReplace = nextState.findIndex(currentDevice => currentDevice.id === device.id) - if (indexToReplace === -1) { - return nextState.concat(extendedDevice) - } - nextState[indexToReplace] = extendedDevice - return nextState - }) - - const isFoundDeviceUpdateNecessary = (currentDevices: DeviceExtendedByUpdateTime[], updatedDevice: Device) => { - const currentDevice = currentDevices.find(({ id }) => updatedDevice.id === id) - if (!currentDevice) { - return true - } - return currentDevice.updateTimestamp < Date.now() - } - - const onConnectSuccess = () => { - navigation.navigate('DEVICE_DETAILS_SCREEN') - setIsConnecting(false) - } - - const onConnectFail = () => { - setIsConnecting(false) - } - - const deviceRender = (device: Device) => ( - { - setIsConnecting(true) - BLEService.connectToDevice(pickedDevice.id).then(onConnectSuccess).catch(onConnectFail) - }} - key={device.id} - device={device} - /> - ) - - return ( - - {isConnecting && ( - - Connecting - - )} - { - setFoundDevices([]) - BLEService.initializeBLE().then(() => BLEService.scanDevices(addFoundDevice, null, true)) - }} - /> - { - setFoundDevices([]) - BLEService.initializeBLE().then(() => BLEService.scanDevices(addFoundDevice, null, false)) - }} - /> - - navigation.navigate('DEVICE_NRF_TEST_SCREEN')} /> - BLEService.isDeviceWithIdConnected('asd')} /> - navigation.navigate('DEVICE_CONNECT_DISCONNECT_TEST_SCREEN')} - /> - navigation.navigate('INSTANCE_DESTROY_SCREEN')} /> - navigation.navigate('DEVICE_ON_DISCONNECT_TEST_SCREEN')} /> - deviceRender(item)} - keyExtractor={device => device.id} - /> - - ) -} diff --git a/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx b/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx deleted file mode 100644 index bab6d54cd..000000000 --- a/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import React, { useRef, useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { BleError, Characteristic, Device, type Subscription, type DeviceId, BleErrorCode } from 'react-native-ble-plx' -import { Alert, ScrollView } from 'react-native' -import base64 from 'react-native-base64' -import Toast from 'react-native-toast-message' -import type { TestStateType } from '../../../types' -import { BLEService, usePersistentDeviceName } from '../../../services' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { currentTimeCharacteristic, deviceTimeService } from '../../../consts/nRFDeviceConsts' -import { wait } from '../../../utils/wait' - -type DeviceConnectDisconnectTestScreenProps = NativeStackScreenProps< - MainStackParamList, - 'DEVICE_CONNECT_DISCONNECT_TEST_SCREEN' -> -const NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO = 10 -const CONNECTION_TIMEOUT = 5000 - -export function DeviceConnectDisconnectTestScreen(_props: DeviceConnectDisconnectTestScreenProps) { - const { deviceName: expectedDeviceName, setDeviceName: setExpectedDeviceName } = usePersistentDeviceName() - const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') - const [deviceId, setDeviceId] = useState('') - const [connectCounter, setConnectCounter] = useState(0) - const [characteristicDiscoverCounter, setCharacteristicDiscoverCounter] = useState(0) - const [connectInDisconnectTestCounter, setConnectInDisconnectTestCounter] = useState(0) - const [disconnectCounter, setDisconnectCounter] = useState(0) - const [monitorMessages, setMonitorMessages] = useState([]) - const monitorSubscriptionRef = useRef(null) - const [timeoutTestState, setTimeoutTestState] = useState('WAITING') - const [timeoutTestLabel, setTimeoutTestLabel] = useState('Timeout Test') - - const startTimeoutTest = async () => { - await BLEService.initializeBLE() - setTimeoutTestLabel('Running') - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - BLEService.stopDeviceScan() - Alert.alert( - 'Prepare for Timeout Test', - 'Please turn off Bluetooth on the device you want to connect to. Press OK when ready.', - [ - { - text: 'OK', - onPress: () => runTimeoutTest(device) - } - ] - ) - } - }, - [deviceTimeService] - ) - } - - const runTimeoutTest = async (device: Device) => { - setTimeoutTestState('IN_PROGRESS') - - try { - await BLEService.connectToDevice(device.id, CONNECTION_TIMEOUT, true) - setTimeoutTestState('ERROR') - setTimeoutTestLabel('Device was able to connect') - } catch (error) { - if ( - error instanceof Error && - (error.message === 'Operation was cancelled' || error.message === 'Operation timed out') - ) { - setTimeoutTestState('DONE') - setTimeoutTestLabel('Success') - } else { - console.error('Unexpected error during timeout test:', error) - setTimeoutTestState('ERROR') - setTimeoutTestLabel('Error occurred') - } - } - } - - const addMonitorMessage = (message: string) => setMonitorMessages(prevMessages => [...prevMessages, message]) - - const checkDeviceName = (device: Device) => - device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase() - - const startConnectAndDiscover = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices(connectAndDiscoverOnDeviceFound, [deviceTimeService]) - } - - const startConnectAndDisconnect = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices(connectAndDisconnectOnDeviceFound, [deviceTimeService]) - } - - const startConnectOnly = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - setConnectCounter(prevCount => prevCount + 1) - setTestScanDevicesState('DONE') - setDeviceId(device.id) - } - }, - [deviceTimeService] - ) - } - - const connectAndDiscoverOnDeviceFound = async (device: Device) => { - if (checkDeviceName(device)) { - setTestScanDevicesState('DONE') - setDeviceId(device.id) - try { - for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - setConnectCounter(prevCount => prevCount + 1) - console.info(`discovering in ${device.id}`) - await startDiscoverServices() - setCharacteristicDiscoverCounter(prevCount => prevCount + 1) - } - console.info('Multiple connect success') - } catch (error) { - console.error('Multiple connect error') - } - } - } - const connectAndDisconnectOnDeviceFound = async (device: Device) => { - if (checkDeviceName(device)) { - setTestScanDevicesState('DONE') - setDeviceId(device.id) - try { - for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) { - await startConnectToDevice(device) - console.info(`connecting to ${device.id}`) - setConnectInDisconnectTestCounter(prevCount => prevCount + 1) - await startDisconnect(device) - console.info(`disconnecting from ${device.id}`) - setDisconnectCounter(prevCount => prevCount + 1) - } - console.info('connect/disconnect success') - } catch (error) { - console.error('Connect/disconnect error') - } - } - } - - const discoverCharacteristicsOnly = async () => { - if (!deviceId) { - console.error('Device not ready') - return - } - try { - for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) { - console.info(`discovering in ${deviceId}`) - await startDiscoverServices() - setCharacteristicDiscoverCounter(prevCount => prevCount + 1) - } - console.info('Multiple discovering success') - } catch (error) { - console.error('Multiple discovering error') - } - } - - const startConnectToDevice = (device: Device) => BLEService.connectToDevice(device.id) - - const startDiscoverServices = () => BLEService.discoverAllServicesAndCharacteristicsForDevice() - - const startDisconnect = (device: Device) => BLEService.disconnectDeviceById(device.id) - - const startCharacteristicMonitor = (directDeviceId?: DeviceId) => { - if (!deviceId && !directDeviceId) { - console.error('Device not ready') - return - } - monitorSubscriptionRef.current = BLEService.setupCustomMonitor( - directDeviceId || deviceId, - deviceTimeService, - currentTimeCharacteristic, - characteristicListener - ) - } - - const characteristicListener = (error: BleError | null, characteristic: Characteristic | null) => { - if (error) { - if (error.errorCode === BleErrorCode.ServiceNotFound || error.errorCode === BleErrorCode.ServicesNotDiscovered) { - startDiscoverServices().then(() => startCharacteristicMonitor()) - return - } - console.error(JSON.stringify(error)) - } - if (characteristic) { - if (characteristic.value) { - const message = base64.decode(characteristic.value) - console.info(message) - addMonitorMessage(message) - } - } - } - - const setupOnDeviceDisconnected = (directDeviceId?: DeviceId) => { - if (!deviceId && !directDeviceId) { - console.error('Device not ready') - return - } - BLEService.onDeviceDisconnectedCustom(directDeviceId || deviceId, disconnectedListener) - } - - const disconnectedListener = (error: BleError | null, device: Device | null) => { - if (error) { - console.error('onDeviceDisconnected') - console.error(JSON.stringify(error, null, 4)) - } - if (device) { - console.info(JSON.stringify(device, null, 4)) - } - } - - // https://github.com/dotintent/react-native-ble-plx/issues/1103 - const showIssue1103Crash = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - setConnectCounter(prevCount => prevCount + 1) - setTestScanDevicesState('DONE') - setDeviceId(device.id) - await startDiscoverServices() - await wait(1000) - setupOnDeviceDisconnected(device.id) - await wait(1000) - startCharacteristicMonitor(device.id) - await wait(1000) - const info = 'Now disconnect device' - console.info(info) - Toast.show({ - type: 'info', - text1: info - }) - } - }, - [deviceTimeService] - ) - } - - return ( - - - - - - setupOnDeviceDisconnected()} /> - - - - - - - - - - - startCharacteristicMonitor()} /> - - - - - ) -} diff --git a/example/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx b/example/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx deleted file mode 100644 index 99f21d898..000000000 --- a/example/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { ScrollView } from 'react-native' -import { AppText, ScreenDefaultContainer } from '../../../components/atoms' -import type { MainStackParamList } from '../../../navigation/navigators' -import { BLEService } from '../../../services' - -type DeviceDetailsScreenProps = NativeStackScreenProps - -export function DeviceScreen(_props: DeviceDetailsScreenProps) { - const connectedDevice = BLEService.getDevice() - return ( - - - {JSON.stringify(connectedDevice, null, 4)} - - - ) -} diff --git a/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx b/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx deleted file mode 100644 index 5ae8b94f9..000000000 --- a/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { BleError, Device, type Subscription, type DeviceId } from 'react-native-ble-plx' -import { ScrollView } from 'react-native' -import Toast from 'react-native-toast-message' -import { wait } from '../../../utils/wait' -import type { TestStateType } from '../../../types' -import { BLEService, usePersistentDeviceName } from '../../../services' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { deviceTimeService } from '../../../consts/nRFDeviceConsts' - -type DeviceOnDisconnectTestScreenProps = NativeStackScreenProps - -export function DeviceOnDisconnectTestScreen(_props: DeviceOnDisconnectTestScreenProps) { - const { deviceName: expectedDeviceName, setDeviceName: setExpectedDeviceName } = usePersistentDeviceName() - const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') - const [deviceId, setDeviceId] = useState('') - const [currentTest, setCurrentTest] = useState(null) - const onDisconnectRef = useRef(null) - - const checkDeviceName = (device: Device) => - device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase() - - const connectAndDiscover = async () => { - setTestScanDevicesState('IN_PROGRESS') - await BLEService.initializeBLE() - await BLEService.scanDevices( - async (device: Device) => { - if (checkDeviceName(device)) { - console.info(`connecting to ${device.id}`) - await startConnectToDevice(device) - await BLEService.discoverAllServicesAndCharacteristicsForDevice() - setTestScanDevicesState('DONE') - setDeviceId(device.id) - } - }, - [deviceTimeService] - ) - } - - const startConnectToDevice = (device: Device) => BLEService.connectToDevice(device.id) - - const setupOnDeviceDisconnected = useCallback( - (directDeviceId?: DeviceId) => { - if (!deviceId && !directDeviceId) { - console.error('Device not ready') - return - } - if (onDisconnectRef.current?.remove) { - onDisconnectRef.current.remove() - onDisconnectRef.current = null - } - onDisconnectRef.current = BLEService.onDeviceDisconnectedCustom(directDeviceId || deviceId, disconnectedListener) - console.info('on device disconnected ready') - }, - [deviceId] - ) - - const disconnectedListener = (error: BleError | null, device: Device | null) => { - console.warn('Disconnect listener called') - if (error) { - console.error('onDeviceDisconnected error') - } - if (device) { - console.info('onDeviceDisconnected device') - } - setDeviceId('') - setCurrentTest(null) - } - - // https://github.com/dotintent/react-native-ble-plx/issues/1126 - const start1126Test = () => connectAndDiscover().then(() => setCurrentTest('disconnectByPLX')) - - // https://github.com/dotintent/react-native-ble-plx/issues/1126 - const start1126DeviceTest = () => connectAndDiscover().then(() => setCurrentTest('disconnectByDevice')) - - const disconnectByPLX = useCallback(async () => { - try { - setupOnDeviceDisconnected() - await wait(1000) - console.info('expected warn: "Disconnect listener called"') - BLEService.disconnectDevice() - console.info('Finished') - } catch (error) { - console.error(error) - } - }, [setupOnDeviceDisconnected]) - - const disconnectByDevice = useCallback(async () => { - try { - setupOnDeviceDisconnected() - wait(1000) - Toast.show({ - type: 'info', - text1: 'Disconnect device', - text2: 'and expect warn: "Disconnect listener called"' - }) - console.info('Disconnect device and expect warn: "Disconnect listener called"') - } catch (error) { - console.error(error) - } - }, [setupOnDeviceDisconnected]) - - useEffect(() => { - if (!deviceId) { - return - } - if (currentTest === 'disconnectByPLX') { - disconnectByPLX() - } - if (currentTest === 'disconnectByDevice') { - disconnectByDevice() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [deviceId]) - - return ( - - - - - setupOnDeviceDisconnected()} /> - - start1126Test()} /> - start1126DeviceTest()} /> - - - ) -} diff --git a/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx b/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx deleted file mode 100644 index 35b70499b..000000000 --- a/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx +++ /dev/null @@ -1,709 +0,0 @@ -import React, { useState, type Dispatch } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { Device, type Base64 } from 'react-native-ble-plx' -import { Platform, ScrollView, Alert } from 'react-native' -import base64 from 'react-native-base64' -import type { TestStateType } from '../../../types' -import { BLEService, usePersistentDeviceName } from '../../../services' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { wait } from '../../../utils/wait' -import { - currentTimeCharacteristic, - currentTimeCharacteristicTimeTriggerDescriptor, - currentTimeCharacteristicTimeTriggerDescriptorValue, - deviceTimeCharacteristic, - deviceTimeService, - monitorExpectedMessage, - writeWithResponseBase64Time, - writeWithoutResponseBase64Time -} from '../../../consts/nRFDeviceConsts' -import { isAndroidSdkAbove34 } from '../../../utils/isAndroidAbove14' - -type DevicenRFTestScreenProps = NativeStackScreenProps - -export function DevicenRFTestScreen(_props: DevicenRFTestScreenProps) { - const { deviceName: expectedDeviceName, setDeviceName: setExpectedDeviceName } = usePersistentDeviceName() - const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') - const [testDeviceConnectedState, setTestDeviceConnectedState] = useState('WAITING') - const [testDiscoverServicesAndCharacteristicsFoundState, setTestDiscoverServicesAndCharacteristicsFoundState] = - useState('WAITING') - - const [testDeviceTimeCharacteristicWriteWithResponseState, setTestDeviceTimeCharacteristicWriteWithResponseState] = - useState('WAITING') - const [ - testDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState - ] = useState('WAITING') - const [ - testDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseStateValue, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseStateValue - ] = useState('') - - const [ - testWriteDeviceTimeCharacteristicWithoutResponseForDeviceState, - setTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState - ] = useState('WAITING') - const [ - testReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState - ] = useState('WAITING') - const [ - testReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseStateValue, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseStateValue - ] = useState('') - - const [testReadTimeTriggerDescriptorForDeviceState, setTestReadTimeTriggerDescriptorForDeviceState] = - useState('WAITING') - const [testWriteTimeTriggerDescriptorForDeviceState, setTestWriteTimeTriggerDescriptorForDeviceState] = - useState('WAITING') - - const [testServicesForDeviceState, setTestServicesForDeviceState] = useState('WAITING') - const [testServicesForDeviceStateValue, setTestServicesForDeviceStateValue] = useState('') - - const [testCharacteristicsForDeviceState, setTestCharacteristicsForDeviceState] = useState('WAITING') - const [testCharacteristicsForDeviceStateValue, setTestCharacteristicsForDeviceStateValue] = useState('') - - const [testDescriptorsForDeviceState, setTestDescriptorsForDeviceState] = useState('WAITING') - const [testDescriptorsForDeviceStateValue, setTestDescriptorsForDeviceStateValue] = useState('') - - const [testIsDeviceConnectedStateState, setTestIsDeviceConnectedStateState] = useState('WAITING') - const [testOnDeviceDisconnectState, setTestOnDeviceDisconnectState] = useState('WAITING') - const [testConnectedDevicesState, setTestConnectedDevicesState] = useState('WAITING') - const [testRequestMTUForDeviceState, setTestRequestMTUForDeviceState] = useState('WAITING') - const [testCancelTransactionState, setTestCancelTransactionState] = useState('WAITING') - const [testReadRSSIForDeviceState, setTestReadRSSIForDeviceState] = useState('WAITING') - const [testGetDevicesState, setTestGetDevicesState] = useState('WAITING') - const [testBTStateState, setTestBTStateState] = useState('WAITING') - const [testRequestConnectionPriorityForDeviceState, setTestRequestConnectionPriorityForDeviceState] = - useState('WAITING') - const [testStartCancelDeviceConnectionState, setTestStartCancelDeviceConnectionState] = - useState('WAITING') - - const [testMonitorCurrentTimeCharacteristicForDevice, setTestMonitorCurrentTimeCharacteristicForDevice] = - useState('WAITING') - const [testDeviceDisconnectState, setTestDeviceDisconnectState] = useState('WAITING') - - const [testEnableState, setTestEnableState] = useState('WAITING') - const [testDisableState, setTestDisableState] = useState('WAITING') - - const onStartHandler = async () => { - setTestDeviceConnectedState('WAITING') - setTestDiscoverServicesAndCharacteristicsFoundState('WAITING') - setTestDeviceTimeCharacteristicWriteWithResponseState('WAITING') - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState('WAITING') - setTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState('WAITING') - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState('WAITING') - setTestReadTimeTriggerDescriptorForDeviceState('WAITING') - setTestWriteTimeTriggerDescriptorForDeviceState('WAITING') - setTestServicesForDeviceState('WAITING') - setTestCharacteristicsForDeviceState('WAITING') - setTestDescriptorsForDeviceState('WAITING') - setTestIsDeviceConnectedStateState('WAITING') - setTestOnDeviceDisconnectState('WAITING') - setTestConnectedDevicesState('WAITING') - setTestRequestMTUForDeviceState('WAITING') - setTestCancelTransactionState('WAITING') - setTestReadRSSIForDeviceState('WAITING') - setTestEnableState('WAITING') - setTestDisableState('WAITING') - setTestGetDevicesState('WAITING') - setTestMonitorCurrentTimeCharacteristicForDevice('WAITING') - setTestDeviceDisconnectState('WAITING') - setTestScanDevicesState('WAITING') - setTestStartCancelDeviceConnectionState('WAITING') - BLEService.initializeBLE().then(scanDevices) - } - - const onDeviceFound = (device: Device) => { - if (device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase()) { - setTestScanDevicesState('DONE') - startConnectToDevice(device) - .then(onDeviceDisconnected) - .then(startTestDiscoverServicesAndCharacteristicsFoundState) - .then(startWriteCharacteristicWithResponseForDevice) - .then(() => - startReadCharacteristicForDevice( - writeWithResponseBase64Time, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseState, - setTestDeviceTimeReadCharacteristicForDeviceAfterWriteWithResponseStateValue - ) - ) - .then(startTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState) - .then(() => - startReadCharacteristicForDevice( - writeWithoutResponseBase64Time, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseState, - setTestReadDeviceTimeCharacteristicForDeviceAfterWriteWithoutResponseStateValue - ) - ) - .then(startWriteTimeTriggerDescriptorForDevice) - .then(startReadTimeTriggerDescriptorForDevice) - .then(startTestGetServicesForDeviceState) - .then(startTestGetCharacteristicsForDeviceState) - .then(startTestGetDescriptorsForDeviceState) - .then(startIsDeviceConnectedState) - .then(startGetConnectedDevices) - .then(startRequestMTUForDevice) - .then(startTestCancelTransaction) - .then(startReadRSSIForDevice) - .then(startGetDevices) - .then(startGetState) - .then(startRequestConnectionPriorityForDevice) - .then(startTestMonitorCurrentTimeCharacteristicForDevice) - .then(disconnectDevice) - .then(startCancelDeviceConnection) - .then(startDisableEnableTest) - .catch(error => console.error(error.message)) - } - } - - const startTestInfo = (testName: string) => console.info('starting: ', testName) - - const runTest = (functionToRun: () => Promise, stateSetter: Dispatch, testName: string) => { - startTestInfo(testName) - stateSetter('IN_PROGRESS') - return functionToRun() - .then(() => { - console.info('success') - stateSetter('DONE') - }) - .catch(error => { - console.error(error) - stateSetter('ERROR') - }) - } - - const scanDevices = () => { - startTestInfo('scanDevices') - setTestScanDevicesState('IN_PROGRESS') - BLEService.scanDevices(onDeviceFound, [deviceTimeService]) - } - - const startConnectToDevice = (device: Device) => - runTest(() => BLEService.connectToDevice(device.id), setTestDeviceConnectedState, 'ConnectToDevice') - - const startTestDiscoverServicesAndCharacteristicsFoundState = () => - runTest( - BLEService.discoverAllServicesAndCharacteristicsForDevice, - setTestDiscoverServicesAndCharacteristicsFoundState, - 'startTestDiscoverServicesAndCharacteristicsFoundState' - ) - - const startWriteCharacteristicWithResponseForDevice = () => - runTest( - () => - BLEService.writeCharacteristicWithResponseForDevice( - deviceTimeService, - deviceTimeCharacteristic, - writeWithResponseBase64Time - ), - setTestDeviceTimeCharacteristicWriteWithResponseState, - 'startWriteCharacteristicWithResponseForDevice' - ) - - const startReadCharacteristicForDevice = ( - expectedValue: Base64, - stateSetFunction: Dispatch, - valueSetter: Dispatch - ) => { - startTestInfo('startReadCharacteristicForDevice') - stateSetFunction('IN_PROGRESS') - return BLEService.readCharacteristicForDevice(deviceTimeService, deviceTimeCharacteristic) - .then(characteristic => { - if (characteristic.value === expectedValue) { - stateSetFunction('DONE') - console.info('success') - valueSetter(characteristic.value) - } else { - throw new Error('Read error') - } - }) - .catch(error => { - console.error(error) - stateSetFunction('ERROR') - }) - } - - const startTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState = () => - runTest( - () => - BLEService.writeCharacteristicWithoutResponseForDevice( - deviceTimeService, - deviceTimeCharacteristic, - writeWithoutResponseBase64Time - ), - setTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState, - 'startTestWriteDeviceTimeCharacteristicWithoutResponseForDeviceState' - ) - - const startTestMonitorCurrentTimeCharacteristicForDevice = () => - new Promise((resolve, reject) => { - startTestInfo('startTestMonitorCurrentTimeCharacteristicForDevice') - setTestMonitorCurrentTimeCharacteristicForDevice('IN_PROGRESS') - Alert.alert( - 'Monitor Current Time Characteristic', - `Please send the following message to the device: "${monitorExpectedMessage}"`, - [ - { - text: 'OK' - } - ] - ) - BLEService.setupMonitor( - deviceTimeService, - currentTimeCharacteristic, - async characteristic => { - console.info( - 'startTestMonitorCurrentTimeCharacteristicForDevice', - `received value: ${characteristic.value && base64.decode(characteristic.value)}, expected value: ${monitorExpectedMessage}` - ) - if (characteristic.value && base64.decode(characteristic.value) === monitorExpectedMessage) { - setTestMonitorCurrentTimeCharacteristicForDevice('DONE') - await BLEService.finishMonitor() - console.info('success') - resolve() - } - }, - async error => { - console.error(error) - setTestMonitorCurrentTimeCharacteristicForDevice('ERROR') - await BLEService.finishMonitor() - reject() - } - ) - }) - - const startWriteTimeTriggerDescriptorForDevice = () => - runTest( - () => - BLEService.writeDescriptorForDevice( - deviceTimeService, - currentTimeCharacteristic, - currentTimeCharacteristicTimeTriggerDescriptor, - currentTimeCharacteristicTimeTriggerDescriptorValue - ), - setTestWriteTimeTriggerDescriptorForDeviceState, - 'startWriteTimeTriggerDescriptorForDevice' - ) - - const startReadTimeTriggerDescriptorForDevice = () => { - setTestReadTimeTriggerDescriptorForDeviceState('IN_PROGRESS') - startTestInfo('startReadTimeTriggerDescriptorForDevice') - return BLEService.readDescriptorForDevice( - deviceTimeService, - currentTimeCharacteristic, - currentTimeCharacteristicTimeTriggerDescriptor - ) - .then(descriptor => { - if (descriptor?.value === currentTimeCharacteristicTimeTriggerDescriptorValue) { - setTestReadTimeTriggerDescriptorForDeviceState('DONE') - console.info('success') - } else { - throw new Error('Read error') - } - }) - .catch(error => { - console.error(error) - setTestReadTimeTriggerDescriptorForDeviceState('ERROR') - }) - } - - const startTestGetServicesForDeviceState = () => - runTest( - () => - BLEService.getServicesForDevice().then(services => { - if (!services) { - throw new Error('services error') - } - setTestServicesForDeviceStateValue( - JSON.stringify( - services.map(({ isPrimary, deviceID, id, uuid }) => ({ isPrimary, deviceID, id, uuid })), - null, - 4 - ) - ) - }), - setTestServicesForDeviceState, - 'startTestGetServicesForDeviceState' - ) - - const startTestGetCharacteristicsForDeviceState = () => - runTest( - () => - BLEService.getDescriptorsForDevice(deviceTimeService, currentTimeCharacteristic).then(descriptors => { - if (!descriptors) { - throw new Error('descriptors error') - } - setTestDescriptorsForDeviceStateValue( - JSON.stringify( - descriptors.map( - ({ deviceID, id, serviceID, serviceUUID, uuid, value, characteristicID, characteristicUUID }) => ({ - deviceID, - id, - serviceID, - serviceUUID, - uuid, - value, - characteristicID, - characteristicUUID - }) - ), - null, - 4 - ) - ) - }), - setTestCharacteristicsForDeviceState, - 'startTestGetCharacteristicsForDeviceState' - ) - - const startTestGetDescriptorsForDeviceState = () => - runTest( - () => - BLEService.getCharacteristicsForDevice(deviceTimeService).then(characteristics => { - if (!characteristics) { - throw new Error('characteristics error') - } - setTestCharacteristicsForDeviceStateValue( - JSON.stringify( - characteristics.map( - ({ - descriptors, - deviceID, - id, - isIndicatable, - isNotifiable, - isNotifying, - isReadable, - isWritableWithResponse, - isWritableWithoutResponse, - serviceID, - serviceUUID, - uuid, - value - }) => ({ - descriptors, - deviceID, - id, - isIndicatable, - isNotifiable, - isNotifying, - isReadable, - isWritableWithResponse, - isWritableWithoutResponse, - serviceID, - serviceUUID, - uuid, - value - }) - ), - null, - 4 - ) - ) - }), - setTestDescriptorsForDeviceState, - 'startTestGetDescriptorsForDeviceState' - ) - - const startIsDeviceConnectedState = () => - runTest( - () => - BLEService.isDeviceConnected().then(connectionStatus => { - if (!connectionStatus) { - throw new Error('isDeviceConnected error') - } - }), - setTestIsDeviceConnectedStateState, - 'startIsDeviceConnectedState' - ) - - const getConnectedDevices = () => - BLEService.getConnectedDevices([deviceTimeService]).then(connectedDevices => { - if (!connectedDevices) { - throw new Error('getConnectedDevices error') - } - const accurateDevice = connectedDevices.find(device => device.name === expectedDeviceName) - if (!accurateDevice) { - throw new Error('getConnectedDevices device not found') - } - return accurateDevice - }) - - const startGetConnectedDevices = () => - runTest(getConnectedDevices, setTestConnectedDevicesState, 'startGetConnectedDevices') - - const startRequestMTUForDevice = () => { - const expectedMTU = isAndroidSdkAbove34 ? 517 : 40 - - return runTest( - () => - BLEService.requestMTUForDevice(expectedMTU).then(device => { - if (Platform.OS === 'ios') { - return - } - if (!device) { - throw new Error('requestMTUForDevice error') - } - if (device.mtu !== expectedMTU) { - throw new Error('the requested MTU has not been set') - } - }), - setTestRequestMTUForDeviceState, - 'startRequestMTUForDevice' - ) - } - - const testCancelTransaction = async () => - new Promise((resolve, reject) => { - const transactionId = 'mtuRequestTransactionTestId' - BLEService.setupMonitor( - deviceTimeService, - currentTimeCharacteristic, - () => {}, - error => { - if (error.message === 'Operation was cancelled') { - resolve() - } else { - console.error(error) - } - }, - transactionId, - true - ) - BLEService.cancelTransaction(transactionId) - - setTimeout(() => reject(new Error('Cancel transaction timeout')), 5000) - }) - - const startTestCancelTransaction = () => - runTest(testCancelTransaction, setTestCancelTransactionState, 'startTestCancelTransaction') - - const disconnectDevice = () => runTest(BLEService.disconnectDevice, setTestDeviceDisconnectState, 'disconnectDevice') - - const startReadRSSIForDevice = () => - runTest( - () => - BLEService.readRSSIForDevice().then(device => { - if (!device) { - throw new Error('readRSSIForDevice error') - } - if (!device.rssi) { - throw new Error('readRSSIForDevice error') - } - }), - setTestReadRSSIForDeviceState, - 'startReadRSSIForDevice' - ) - - const startGetDevices = () => - runTest( - () => - BLEService.getDevices().then(devices => { - if (!devices) { - throw new Error('getDevices error') - } - const device = devices.filter(({ name }) => name === expectedDeviceName) - if (!device) { - throw new Error('getDevices error') - } - }), - setTestGetDevicesState, - 'startGetDevices' - ) - - const startRequestConnectionPriorityForDevice = () => - runTest( - () => - BLEService.requestConnectionPriorityForDevice(1).then(device => { - if (!device) { - throw new Error('getDevices error') - } - }), - setTestRequestConnectionPriorityForDeviceState, - 'startRequestConnectionPriorityForDevice' - ) - - const getState = () => - BLEService.getState().then(bluetoothState => { - if (!bluetoothState) { - throw new Error('getDevices error') - } - return bluetoothState - }) - - const startGetState = () => runTest(getState, setTestBTStateState, 'startGetState') - - const startDisableEnableTest = () => - // eslint-disable-next-line no-async-promise-executor - new Promise(async (resolve, reject) => { - startTestInfo('startDisableEnableTest') - setTestEnableState('IN_PROGRESS') - setTestDisableState('IN_PROGRESS') - if (parseInt(Platform.Version.toString(), 10) >= 33 || Platform.OS === 'ios') { - setTestEnableState('DONE') - setTestDisableState('DONE') - resolve() - return - } - const initialState = await BLEService.getState() - if (initialState === 'PoweredOff') { - await BLEService.enable() - wait(1000) - } - await BLEService.disable() - while (true) { - const expectedPoweredOffState = await BLEService.getState() - if (expectedPoweredOffState === 'Resetting') { - wait(1000) - continue - } - if (expectedPoweredOffState !== 'PoweredOff') { - reject(new Error('BT disable error')) - setTestDisableState('ERROR') - return - } - break - } - setTestDisableState('DONE') - await BLEService.enable() - while (true) { - const expectedPoweredOnState = await BLEService.getState() - if (expectedPoweredOnState === 'Resetting') { - wait(1000) - continue - } - if (expectedPoweredOnState !== 'PoweredOn') { - reject(new Error('BT enable error')) - setTestEnableState('ERROR') - return - } - break - } - setTestEnableState('DONE') - console.info('success') - resolve() - }) - - const onDeviceDisconnected = () => { - if (testOnDeviceDisconnectState === 'DONE') { - return - } - setTestOnDeviceDisconnectState('IN_PROGRESS') - const onDeviceDisconnectedSubscription = BLEService.onDeviceDisconnected((error, device) => { - if (error) { - setTestOnDeviceDisconnectState('ERROR') - } - if (device) { - setTestOnDeviceDisconnectState(prev => { - onDeviceDisconnectedSubscription.remove() - return prev === 'IN_PROGRESS' ? 'DONE' : 'ERROR' - }) - } - }) - } - - const cancelDeviceConnection = () => - new Promise((resolve, reject) => { - BLEService.scanDevices( - (device: Device) => { - if (device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase()) { - BLEService.connectToDevice(device.id) - .then(() => BLEService.cancelDeviceConnection()) - .then(() => resolve()) - .catch(error => { - if (error?.message === `Device ${device.id} was disconnected`) { - resolve() - } - reject(error) - }) - } - }, - [deviceTimeService] - ).catch(reject) - }) - - const startCancelDeviceConnection = () => - runTest(cancelDeviceConnection, setTestStartCancelDeviceConnectionState, 'startCancelDeviceConnection') - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/example/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx b/example/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx deleted file mode 100644 index 8cf621add..000000000 --- a/example/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useState } from 'react' -import type { NativeStackScreenProps } from '@react-navigation/native-stack' -import { BleError } from 'react-native-ble-plx' -import { ScrollView, View } from 'react-native' -import type { MainStackParamList } from '../../../navigation/navigators' -import { AppButton, AppText, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' -import { functionsToTest } from './utils' -import { BLEService } from '../../../services' -import { type TestStateType } from '../../../types' - -type DeviceConnectDisconnectTestScreenProps = NativeStackScreenProps -type TestData = { name: string; response: string | null } - -export function InstanceDestroyScreen(_props: DeviceConnectDisconnectTestScreenProps) { - const [dataReads, setDataReads] = useState<(TestData | string)[]>([]) - const [instanceExistsCalls, setInstanceExistsCalls] = useState(0) - const [instanceDestroyedCalls, setInstanceDestroyedCalls] = useState(0) - const [instanceDestroyedCallsWithCorrectInfo, setInstanceDestroyedCallsWithCorrectInfo] = useState(0) - const [correctInstaceDestroy, setCorrectInstaceDestroy] = useState('WAITING') - const [secondInstaceDestroyFinishedWithError, setSecondInstaceDestroyFinishedWithError] = - useState('WAITING') - - const callPromise = async (promise: Promise) => - promise - .then(value => { - const status = value?.toString() || 'finished' - console.info(status) - return status - }) - .catch((error: BleError) => { - const { reason } = error - if (reason) { - console.error(reason) - return reason - } - console.error(error) - return 'Error' - }) - - const startChain = async (increaseCounter: () => void) => { - for (let i = 0; i < functionsToTest.length; i += 1) { - try { - const testObject = functionsToTest[i] - if (testObject) { - const { name, functionToCall } = testObject - console.info(`${i} - ${name}`) - const response = await callPromise(functionToCall()) - if (response && response.includes('BleManager has been destroyed')) { - setInstanceDestroyedCallsWithCorrectInfo(prevState => prevState + 1) - } - addDataToTimeReads({ - name, - response - }) - increaseCounter() - } else { - addDataToTimeReads({ - name: `index-${i}`, - response: '-----ERROR-----' - }) - } - } catch (e) { - console.info(`PROBLEM WITH INDEX - ${i}`) - console.error(e) - } - } - } - - const addDataToTimeReads = ({ name, response }: TestData) => { - setDataReads(prevState => - prevState.concat({ - name, - response - }) - ) - } - - const startTest = async () => { - await startChain(() => setInstanceExistsCalls(prevState => prevState + 1)) - await BLEService.manager - .destroy() - .then(info => { - console.info('first destroy try - then', info) - setCorrectInstaceDestroy('DONE') - }) - .catch(error => { - console.error('first destroy try - catch', error) - setCorrectInstaceDestroy('ERROR') - }) - await BLEService.manager - .destroy() - .then(info => { - console.info('second destroy try - then', info) - setSecondInstaceDestroyFinishedWithError('ERROR') - }) - .catch(error => { - console.error('second destroy try - catch', error) - setSecondInstaceDestroyFinishedWithError( - error?.reason?.includes('BleManager has been destroyed') ? 'DONE' : 'ERROR' - ) - }) - - await startChain(() => setInstanceDestroyedCalls(prevState => prevState + 1)) - } - - const timeEntriesToRender = dataReads.map((entry, index) => { - if (typeof entry === 'object') { - const { name, response } = entry - - return ( - - {name} - result: {response} - - ) - } - - return ( - - {entry} - - ) - }) - - return ( - - - - It can get stuck on several functions per minute - - Finished calls with existing instance: {instanceExistsCalls}/{functionsToTest.length} - - - - - Finished calls with destroyed instance: {instanceDestroyedCalls}/{functionsToTest.length} - - - Finished calls with correct info about instance destroyed: {instanceDestroyedCallsWithCorrectInfo}/ - {functionsToTest.length} - - {timeEntriesToRender} - - - ) -} diff --git a/example/src/screens/MainStack/InstanceDestroyScreen/utils.ts b/example/src/screens/MainStack/InstanceDestroyScreen/utils.ts deleted file mode 100644 index 8f73e0f75..000000000 --- a/example/src/screens/MainStack/InstanceDestroyScreen/utils.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { ConnectionPriority, LogLevel } from 'react-native-ble-plx' -import { BLEService } from '../../../services' -import { deviceTimeCharacteristic, deviceTimeService } from '../../../consts/nRFDeviceConsts' - -const TEST_DEVICE_ID = '5F:0A:E8:F1:11:11' - -export const functionsToTest: { name: string; functionToCall: () => Promise }[] = [ - { - name: 'setLogLevel', - functionToCall: () => BLEService.manager.setLogLevel(LogLevel.Verbose) - }, - { - name: 'cancelTransaction', - functionToCall: () => BLEService.manager.cancelTransaction('transactionId') - }, - { - name: 'state', - functionToCall: () => BLEService.manager.state() - }, - { - name: 'startDeviceScan', - functionToCall: () => BLEService.manager.startDeviceScan(null, null, () => {}) - }, - { - name: 'stopDeviceScan', - functionToCall: () => BLEService.manager.stopDeviceScan() - }, - { - name: 'requestConnectionPriorityForDevice', - functionToCall: () => BLEService.manager.requestConnectionPriorityForDevice(TEST_DEVICE_ID, ConnectionPriority.High) - }, - { - name: 'readRSSIForDevice', - functionToCall: () => BLEService.manager.readRSSIForDevice(TEST_DEVICE_ID) - }, - { - name: 'requestMTUForDevice', - functionToCall: () => BLEService.manager.requestMTUForDevice(TEST_DEVICE_ID, 300) - }, - { - name: 'devices', - functionToCall: () => BLEService.manager.devices([TEST_DEVICE_ID]) - }, - { - name: 'connectedDevices', - functionToCall: () => BLEService.manager.connectedDevices([deviceTimeService]) - }, - { - name: 'connectToDevice', - functionToCall: () => BLEService.manager.connectToDevice(TEST_DEVICE_ID) - }, - { - name: 'cancelDeviceConnection', - functionToCall: () => BLEService.manager.cancelDeviceConnection(TEST_DEVICE_ID) - }, - { - name: 'isDeviceConnected', - functionToCall: () => BLEService.manager.isDeviceConnected(TEST_DEVICE_ID) - }, - { - name: 'discoverAllServicesAndCharacteristicsForDevice', - functionToCall: () => BLEService.manager.discoverAllServicesAndCharacteristicsForDevice(TEST_DEVICE_ID) - }, - { - name: 'servicesForDevice', - functionToCall: () => BLEService.manager.servicesForDevice(TEST_DEVICE_ID) - }, - { - name: 'characteristicsForDevice', - functionToCall: () => BLEService.manager.characteristicsForDevice(TEST_DEVICE_ID, deviceTimeService) - }, - { - name: 'descriptorsForDevice', - functionToCall: () => - BLEService.manager.descriptorsForDevice(TEST_DEVICE_ID, deviceTimeService, deviceTimeCharacteristic) - }, - { - name: 'readCharacteristicForDevice', - functionToCall: () => - BLEService.manager.readCharacteristicForDevice(TEST_DEVICE_ID, deviceTimeService, deviceTimeCharacteristic) - }, - { - name: 'writeCharacteristicWithResponseForDevice', - functionToCall: () => - BLEService.manager.writeCharacteristicWithResponseForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - 'base64Value' - ) - }, - { - name: 'writeCharacteristicWithoutResponseForDevice', - functionToCall: () => - BLEService.manager.writeCharacteristicWithoutResponseForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - 'base64Value' - ) - }, - { - name: 'readDescriptorForDevice', - functionToCall: () => - BLEService.manager.readDescriptorForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - deviceTimeCharacteristic - ) - }, - { - name: 'writeDescriptorForDevice', - functionToCall: () => - BLEService.manager.writeDescriptorForDevice( - TEST_DEVICE_ID, - deviceTimeService, - deviceTimeCharacteristic, - deviceTimeCharacteristic, - 'Base64' - ) - }, - { - name: 'disable', - functionToCall: () => BLEService.manager.disable() - }, - { - name: 'enable', - functionToCall: () => BLEService.manager.enable() - } -] as const diff --git a/example/src/screens/MainStack/index.ts b/example/src/screens/MainStack/index.ts deleted file mode 100644 index 6887aa49e..000000000 --- a/example/src/screens/MainStack/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './DashboardScreen/DashboardScreen' -export * from './DeviceDetailsScreen/DeviceDetailsScreen' -export * from './DevicenRFTestScreen/DevicenRFTestScreen' -export * from './DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen' -export * from './InstanceDestroyScreen/InstanceDestroyScreen' -export * from './DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen' diff --git a/example/src/screens/ScanScreen.tsx b/example/src/screens/ScanScreen.tsx new file mode 100644 index 000000000..8ee13dd7b --- /dev/null +++ b/example/src/screens/ScanScreen.tsx @@ -0,0 +1,234 @@ +import React, { useEffect, useState, useCallback, useRef } from 'react'; +import { + View, + Text, + TouchableOpacity, + Pressable, + TextInput, + StyleSheet, + Platform, + PermissionsAndroid, + Alert, +} from 'react-native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { ScanResult } from 'react-native-ble-plx'; +import type { RootStackParamList } from '../App'; + +type Props = NativeStackScreenProps; + +async function requestAndroidPermissions(): Promise { + if (Platform.OS !== 'android') return true; + + try { + const granted = await PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, + PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, + PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, + ]); + return Object.values(granted).every( + (v) => v === PermissionsAndroid.RESULTS.GRANTED, + ); + } catch { + return false; + } +} + +export default function ScanScreen({ navigation, route }: Props) { + const manager = route.params.manager; + const [scanning, setScanning] = useState(false); + const [bleState, setBleState] = useState('Unknown'); + const [devices, setDevices] = useState>(new Map()); + const [filterEnabled, setFilterEnabled] = useState(false); + const [searchText, setSearchText] = useState(''); + const [connectionStatus, setConnectionStatus] = useState(''); + const [scanError, setScanError] = useState(null); + const devicesRef = useRef>(new Map()); + + const TEST_SERVICE_UUID = '12345678-1234-1234-1234-123456789ABC'; + + useEffect(() => { + const sub = manager.onStateChange((state: string) => { + setBleState(state); + }, true); + + requestAndroidPermissions().then((ok) => { + if (!ok) { + setScanError('BLE permissions not granted'); + Alert.alert('Permissions', 'BLE permissions not granted'); + } + }); + + return () => sub.remove(); + }, [manager]); + + const startScan = useCallback(() => { + manager.stopDeviceScan(); + devicesRef.current = new Map(); + setDevices(new Map()); + setScanning(true); + setConnectionStatus(''); + setScanError(null); + + const uuids = filterEnabled ? [TEST_SERVICE_UUID] : null; + manager.startDeviceScan(uuids, null, (error: any, result: ScanResult | null) => { + if (error) { + console.warn('Scan error:', error); + setScanError(error.message || String(error)); + setScanning(false); + return; + } + if (result) { + devicesRef.current.set(result.id, result); + setDevices(new Map(devicesRef.current)); + } + }); + }, [manager]); + + const stopScan = useCallback(() => { + manager.stopDeviceScan(); + setScanning(false); + }, [manager]); + + const connectToDevice = useCallback( + async (device: ScanResult) => { + setConnectionStatus(`Connecting to ${device.name || device.id}...`); + stopScan(); + try { + // Check if already connected — if so, just navigate + const connected = await manager.isDeviceConnected(device.id); + if (connected) { + setConnectionStatus('Already connected, navigating...'); + navigation.navigate('Device', { manager, deviceId: device.id, deviceName: device.name }); + return; + } + const result = await manager.connectToDevice(device.id); + setConnectionStatus('Connected!'); + navigation.navigate('Device', { manager, deviceId: result.id, deviceName: result.name }); + } catch (e: any) { + setConnectionStatus(`Error: ${e.message || String(e)}`); + Alert.alert('Connection Error', e.message || String(e)); + } + }, + [manager, navigation, stopScan], + ); + + const deviceList = Array.from(devices.values()) + .filter((d) => { + if (!searchText) return true; + const q = searchText.toLowerCase(); + return (d.name?.toLowerCase().includes(q) || d.id.toLowerCase().includes(q)); + }) + .sort((a, b) => (b.rssi ?? -999) - (a.rssi ?? -999)); + + return ( + + + BLE Scanner ({Platform.OS}) + + BLE State: {bleState} + + {connectionStatus ? ( + {connectionStatus} + ) : null} + + {scanError && ( + + {scanError} + + )} + + + + Start Scan + + + Stop Scan + + + + + + setFilterEnabled(!filterEnabled)}> + + {filterEnabled ? 'Filter: Test Service UUID' : 'Filter: None'} + + + + + {deviceList.length} device(s) found + + + + {deviceList.map((item) => ( + [ + styles.deviceRow, + pressed && styles.deviceRowPressed, + ]} + onPress={() => connectToDevice(item)}> + + {item.name || 'Unknown Device'} + + {item.id} + RSSI: {item.rssi} + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, backgroundColor: '#fff' }, + titleText: { fontSize: 20, fontWeight: '700', marginBottom: 4 }, + stateText: { fontSize: 16, fontWeight: '600', marginBottom: 8 }, + statusText: { fontSize: 14, fontWeight: '500', color: '#007AFF', marginBottom: 8, padding: 8, backgroundColor: '#E8F0FE', borderRadius: 6 }, + buttonRow: { flexDirection: 'row', gap: 12, marginBottom: 12 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + buttonDisabled: { backgroundColor: '#ccc' }, + buttonText: { color: '#fff', fontWeight: '600' }, + filterButton: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 6, borderWidth: 1, borderColor: '#ccc', alignSelf: 'flex-start', marginBottom: 8 }, + filterActive: { borderColor: '#007AFF', backgroundColor: '#E8F0FE' }, + filterText: { fontSize: 12, color: '#888' }, + filterTextActive: { color: '#007AFF', fontWeight: '600' }, + searchInput: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10, fontSize: 14, marginBottom: 8 }, + countText: { fontSize: 14, color: '#666', marginBottom: 8 }, + deviceRow: { + padding: 12, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + deviceRowPressed: { + backgroundColor: '#f0f0f0', + }, + deviceName: { fontSize: 16, fontWeight: '500' }, + deviceId: { fontSize: 12, color: '#888', marginTop: 2 }, + deviceRssi: { fontSize: 12, color: '#888', marginTop: 2 }, + errorText: { fontSize: 14, color: '#FF3B30', marginBottom: 8, padding: 8, backgroundColor: '#FDE8E8', borderRadius: 6 }, +}); diff --git a/example/src/screens/index.ts b/example/src/screens/index.ts deleted file mode 100644 index 472fafd1e..000000000 --- a/example/src/screens/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './MainStack' diff --git a/example/src/services/BLEService/BLEService.ts b/example/src/services/BLEService/BLEService.ts deleted file mode 100644 index 093ead7ea..000000000 --- a/example/src/services/BLEService/BLEService.ts +++ /dev/null @@ -1,462 +0,0 @@ -import { - BleError, - BleErrorCode, - BleManager, - Device, - State as BluetoothState, - LogLevel, - type DeviceId, - type TransactionId, - type UUID, - type Characteristic, - type Base64, - type Subscription -} from 'react-native-ble-plx' -import { PermissionsAndroid, Platform } from 'react-native' -import Toast from 'react-native-toast-message' - -const deviceNotConnectedErrorText = 'Device is not connected' - -class BLEServiceInstance { - manager: BleManager - - device: Device | null - - characteristicMonitor: Subscription | null - - isCharacteristicMonitorDisconnectExpected = false - - constructor() { - this.device = null - this.characteristicMonitor = null - this.manager = new BleManager() - this.manager.setLogLevel(LogLevel.Verbose) - } - - createNewManager = () => { - this.manager = new BleManager() - this.manager.setLogLevel(LogLevel.Verbose) - } - - getDevice = () => this.device - - initializeBLE = () => - new Promise(resolve => { - const subscription = this.manager.onStateChange(state => { - switch (state) { - case BluetoothState.Unsupported: - this.showErrorToast('') - break - case BluetoothState.PoweredOff: - this.onBluetoothPowerOff() - this.manager.enable().catch((error: BleError) => { - if (error.errorCode === BleErrorCode.BluetoothUnauthorized) { - this.requestBluetoothPermission() - } - }) - break - case BluetoothState.Unauthorized: - this.requestBluetoothPermission() - break - case BluetoothState.PoweredOn: - resolve() - subscription.remove() - break - default: - console.error('Unsupported state: ', state) - // resolve() - // subscription.remove() - } - }, true) - }) - - disconnectDevice = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .cancelDeviceConnection(this.device.id) - .then(() => this.showSuccessToast('Device disconnected')) - .catch(error => { - if (error?.code !== BleErrorCode.DeviceDisconnected) { - this.onError(error) - } - }) - } - - disconnectDeviceById = (id: DeviceId) => - this.manager - .cancelDeviceConnection(id) - .then(() => this.showSuccessToast('Device disconnected')) - .catch(error => { - if (error?.code !== BleErrorCode.DeviceDisconnected) { - this.onError(error) - } - }) - - onBluetoothPowerOff = () => { - this.showErrorToast('Bluetooth is turned off') - } - - scanDevices = async (onDeviceFound: (device: Device) => void, UUIDs: UUID[] | null = null, legacyScan?: boolean) => { - this.manager - .startDeviceScan(UUIDs, { legacyScan }, (error, device) => { - if (error) { - this.onError(error) - console.error(error.message) - this.manager.stopDeviceScan() - return - } - if (device) { - onDeviceFound(device) - } - }) - .then(() => {}) - .catch(console.error) - } - - stopDeviceScan = () => { - this.manager.stopDeviceScan() - } - - connectToDevice = (deviceId: DeviceId, timeout?: number, ignoreError = false) => - new Promise((resolve, reject) => { - this.manager.stopDeviceScan() - this.manager - .connectToDevice(deviceId, { timeout }) - .then(device => { - this.device = device - resolve(device) - }) - .catch(error => { - if (error.errorCode === BleErrorCode.DeviceAlreadyConnected && this.device) { - resolve(this.device) - } else { - if (!ignoreError) { - this.onError(error) - } - reject(error) - } - }) - }) - - discoverAllServicesAndCharacteristicsForDevice = async () => - new Promise((resolve, reject) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - reject(new Error(deviceNotConnectedErrorText)) - return - } - this.manager - .discoverAllServicesAndCharacteristicsForDevice(this.device.id) - .then(device => { - resolve(device) - this.device = device - }) - .catch(error => { - this.onError(error) - reject(error) - }) - }) - - readCharacteristicForDevice = async (serviceUUID: UUID, characteristicUUID: UUID) => - new Promise((resolve, reject) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - reject(new Error(deviceNotConnectedErrorText)) - return - } - this.manager - .readCharacteristicForDevice(this.device.id, serviceUUID, characteristicUUID) - .then(characteristic => { - resolve(characteristic) - }) - .catch(error => { - this.onError(error) - }) - }) - - writeCharacteristicWithResponseForDevice = async (serviceUUID: UUID, characteristicUUID: UUID, time: Base64) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .writeCharacteristicWithResponseForDevice(this.device.id, serviceUUID, characteristicUUID, time) - .catch(error => { - this.onError(error) - }) - } - - writeCharacteristicWithoutResponseForDevice = async (serviceUUID: UUID, characteristicUUID: UUID, time: Base64) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .writeCharacteristicWithoutResponseForDevice(this.device.id, serviceUUID, characteristicUUID, time) - .catch(error => { - this.onError(error) - }) - } - - setupMonitor = ( - serviceUUID: UUID, - characteristicUUID: UUID, - onCharacteristicReceived: (characteristic: Characteristic) => void, - onError: (error: Error) => void, - transactionId?: TransactionId, - hideErrorDisplay?: boolean - ) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - this.characteristicMonitor = this.manager.monitorCharacteristicForDevice( - this.device?.id, - serviceUUID, - characteristicUUID, - (error, characteristic) => { - if (error) { - if (error.errorCode === 2 && this.isCharacteristicMonitorDisconnectExpected) { - this.isCharacteristicMonitorDisconnectExpected = false - return - } - onError(error) - if (!hideErrorDisplay) { - this.onError(error) - this.characteristicMonitor?.remove() - } - return - } - if (characteristic) { - onCharacteristicReceived(characteristic) - } - }, - transactionId - ) - } - - setupCustomMonitor: BleManager['monitorCharacteristicForDevice'] = (...args) => - this.manager.monitorCharacteristicForDevice(...args) - - finishMonitor = () => { - this.isCharacteristicMonitorDisconnectExpected = true - this.characteristicMonitor?.remove() - } - - writeDescriptorForDevice = async ( - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - data: Base64 - ) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .writeDescriptorForDevice(this.device.id, serviceUUID, characteristicUUID, descriptorUUID, data) - .catch(error => { - this.onError(error) - }) - } - - readDescriptorForDevice = async (serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager - .readDescriptorForDevice(this.device.id, serviceUUID, characteristicUUID, descriptorUUID) - .catch(error => { - this.onError(error) - }) - } - - getServicesForDevice = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.servicesForDevice(this.device.id).catch(error => { - this.onError(error) - }) - } - - getCharacteristicsForDevice = (serviceUUID: UUID) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.characteristicsForDevice(this.device.id, serviceUUID).catch(error => { - this.onError(error) - }) - } - - getDescriptorsForDevice = (serviceUUID: UUID, characteristicUUID: UUID) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.descriptorsForDevice(this.device.id, serviceUUID, characteristicUUID).catch(error => { - this.onError(error) - }) - } - - isDeviceConnected = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.isDeviceConnected(this.device.id) - } - - isDeviceWithIdConnected = (id: DeviceId) => this.manager.isDeviceConnected(id).catch(console.error) - - getConnectedDevices = (expectedServices: UUID[]) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.connectedDevices(expectedServices).catch(error => { - this.onError(error) - }) - } - - requestMTUForDevice = (mtu: number) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.requestMTUForDevice(this.device.id, mtu).catch(error => { - this.onError(error) - }) - } - - onDeviceDisconnected = (listener: (error: BleError | null, device: Device | null) => void) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.onDeviceDisconnected(this.device.id, listener) - } - - onDeviceDisconnectedCustom: BleManager['onDeviceDisconnected'] = (...args) => - this.manager.onDeviceDisconnected(...args) - - readRSSIForDevice = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.readRSSIForDevice(this.device.id).catch(error => { - this.onError(error) - }) - } - - getDevices = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.devices([this.device.id]).catch(error => { - this.onError(error) - }) - } - - cancelTransaction = (transactionId: TransactionId) => this.manager.cancelTransaction(transactionId) - - enable = () => - this.manager.enable().catch(error => { - this.onError(error) - }) - - disable = () => - this.manager.disable().catch(error => { - this.onError(error) - }) - - getState = () => - this.manager.state().catch(error => { - this.onError(error) - }) - - onError = (error: BleError) => { - switch (error.errorCode) { - case BleErrorCode.BluetoothUnauthorized: - this.requestBluetoothPermission() - break - case BleErrorCode.LocationServicesDisabled: - this.showErrorToast('Location services are disabled') - break - default: - this.showErrorToast(JSON.stringify(error, null, 4)) - } - } - - requestConnectionPriorityForDevice = (priority: 0 | 1 | 2) => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.requestConnectionPriorityForDevice(this.device?.id, priority) - } - - cancelDeviceConnection = () => { - if (!this.device) { - this.showErrorToast(deviceNotConnectedErrorText) - throw new Error(deviceNotConnectedErrorText) - } - return this.manager.cancelDeviceConnection(this.device?.id) - } - - requestBluetoothPermission = async () => { - if (Platform.OS === 'ios') { - return true - } - if (Platform.OS === 'android') { - const apiLevel = parseInt(Platform.Version.toString(), 10) - - if (apiLevel < 31 && PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) - return granted === PermissionsAndroid.RESULTS.GRANTED - } - if (PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN && PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT) { - const result = await PermissionsAndroid.requestMultiple([ - PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, - PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT - ]) - - return ( - result['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED && - result['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED - ) - } - } - - this.showErrorToast('Permission have not been granted') - - return false - } - - showErrorToast = (error: string) => { - Toast.show({ - type: 'error', - text1: 'Error', - text2: error - }) - console.error(error) - } - - showSuccessToast = (info: string) => { - Toast.show({ - type: 'success', - text1: 'Success', - text2: info - }) - } -} - -export const BLEService = new BLEServiceInstance() diff --git a/example/src/services/index.ts b/example/src/services/index.ts deleted file mode 100644 index ec06f7989..000000000 --- a/example/src/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './BLEService/BLEService' -export * from './storage/persistentDeviceName' diff --git a/example/src/services/storage/persistentDeviceName.ts b/example/src/services/storage/persistentDeviceName.ts deleted file mode 100644 index 4818b1b37..000000000 --- a/example/src/services/storage/persistentDeviceName.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useAsyncStorage } from '@react-native-async-storage/async-storage' -import { useState, useEffect, useCallback } from 'react' - -export const PERSISTENT_DEVICE_NAME_KEY = 'PERSISTENT_DEVICE_NAME' - -export const usePersistentDeviceName = () => { - const { getItem, setItem } = useAsyncStorage(PERSISTENT_DEVICE_NAME_KEY) - const [deviceName, setName] = useState() - - useEffect(() => { - ;(async () => { - if (!deviceName) { - const name = await getItem() - if (name) { - setName(name) - } - } - })() - }, [deviceName, setName, getItem]) - - const setDeviceName = useCallback( - (name: string) => { - setItem(name) - setName(name) - }, - [setItem, setName] - ) - - return { deviceName, setDeviceName } -} diff --git a/example/src/theme/colors.ts b/example/src/theme/colors.ts deleted file mode 100644 index 7679fd50d..000000000 --- a/example/src/theme/colors.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const colors = { - mainRed: '#ff304d' -} as const diff --git a/example/src/theme/sizes.ts b/example/src/theme/sizes.ts deleted file mode 100644 index 7c2ad4bf3..000000000 --- a/example/src/theme/sizes.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const sizes = { - defaultFontSize: 14, - defaultScreenPadding: 12 -} as const diff --git a/example/src/theme/theme.ts b/example/src/theme/theme.ts deleted file mode 100644 index d5b1621b4..000000000 --- a/example/src/theme/theme.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { DefaultTheme } from 'styled-components' -import { colors } from './colors' -import { sizes } from './sizes' - -export const commonTheme: DefaultTheme = { - sizes, - colors -} as const - -export type AppTheme = { sizes: typeof sizes; colors: typeof colors } diff --git a/example/src/types/TestStateType.ts b/example/src/types/TestStateType.ts deleted file mode 100644 index 93465a348..000000000 --- a/example/src/types/TestStateType.ts +++ /dev/null @@ -1 +0,0 @@ -export type TestStateType = 'DONE' | 'WAITING' | 'ERROR' | 'IN_PROGRESS' diff --git a/example/src/types/index.ts b/example/src/types/index.ts deleted file mode 100644 index 3689e2fc7..000000000 --- a/example/src/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TestStateType' diff --git a/example/src/utils/cloneDeep.ts b/example/src/utils/cloneDeep.ts deleted file mode 100644 index 93851e47f..000000000 --- a/example/src/utils/cloneDeep.ts +++ /dev/null @@ -1 +0,0 @@ -export const cloneDeep: (objectToClone: T) => T = objectToClone => JSON.parse(JSON.stringify(objectToClone)) diff --git a/example/src/utils/getCurrentTimeAsBase64.ts b/example/src/utils/getCurrentTimeAsBase64.ts deleted file mode 100644 index 722beab2f..000000000 --- a/example/src/utils/getCurrentTimeAsBase64.ts +++ /dev/null @@ -1,15 +0,0 @@ -import base64 from 'react-native-base64' -import { getDateUint8Array } from './getDateUint8Array' - -export const getCurrentDateAsBase64 = () => { - const date = new Date() - const dateToSend = getDateUint8Array( - date.getFullYear(), - date.getMonth(), - date.getDay(), - date.getHours(), - date.getMinutes(), - date.getSeconds() - ) - return base64.encodeFromByteArray(dateToSend) -} diff --git a/example/src/utils/getDateAsBase64.ts b/example/src/utils/getDateAsBase64.ts deleted file mode 100644 index cc091c085..000000000 --- a/example/src/utils/getDateAsBase64.ts +++ /dev/null @@ -1,14 +0,0 @@ -import base64 from 'react-native-base64' -import { getDateUint8Array } from './getDateUint8Array' - -export const getDateAsBase64 = (date: Date) => { - const dateToSend = getDateUint8Array( - date.getFullYear(), - date.getMonth(), - date.getDay(), - date.getHours(), - date.getMinutes(), - date.getSeconds() - ) - return base64.encodeFromByteArray(dateToSend) -} diff --git a/example/src/utils/getDateUint8Array.ts b/example/src/utils/getDateUint8Array.ts deleted file mode 100644 index 6770bc112..000000000 --- a/example/src/utils/getDateUint8Array.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const getDateUint8Array = ( - year: number, - month: number, - day: number, - hour: number, - minute: number, - second: number -) => { - const yearFirstByte = year >> 8 - const yearSecondByte = year - 2 ** 16 - - return new Uint8Array([yearFirstByte, yearSecondByte, month, day, hour, minute, second]) -} diff --git a/example/src/utils/isAndroidAbove14.ts b/example/src/utils/isAndroidAbove14.ts deleted file mode 100644 index 57530046c..000000000 --- a/example/src/utils/isAndroidAbove14.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Platform } from 'react-native' - -export const isAndroidSdkAbove34 = Platform.OS === 'android' && Platform.Version >= 34 diff --git a/example/src/utils/wait.ts b/example/src/utils/wait.ts deleted file mode 100644 index 44bede3ae..000000000 --- a/example/src/utils/wait.ts +++ /dev/null @@ -1 +0,0 @@ -export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/example/tsconfig.json b/example/tsconfig.json new file mode 100644 index 000000000..266ba9ca2 --- /dev/null +++ b/example/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@react-native/typescript-config", + "compilerOptions": { + "types": ["jest"] + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["**/node_modules", "**/Pods"] +} diff --git a/example/yarn.lock b/example/yarn.lock deleted file mode 100644 index c239b0d5b..000000000 --- a/example/yarn.lock +++ /dev/null @@ -1,6806 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" - -"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" - integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== - -"@babel/compat-data@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" - integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.24.7", "@babel/core@^7.25.2": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.7.tgz#0439347a183b97534d52811144d763a17f9d2b24" - integrity sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.5" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.7" - "@babel/parser" "^7.26.7" - "@babel/template" "^7.25.9" - "@babel/traverse" "^7.26.7" - "@babel/types" "^7.26.7" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/core@^7.20.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" - integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.5" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.24.5" - "@babel/helpers" "^7.24.5" - "@babel/parser" "^7.24.5" - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.5" - "@babel/types" "^7.24.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/eslint-parser@^7.25.1": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz#aa669f4d873f9cd617050cf3c40c19cd96307efb" - integrity sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ== - dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.1" - -"@babel/generator@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" - integrity sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA== - dependencies: - "@babel/types" "^7.24.5" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/generator@^7.25.0", "@babel/generator@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458" - integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw== - dependencies: - "@babel/parser" "^7.26.5" - "@babel/types" "^7.26.5" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== - dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" - integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== - dependencies: - "@babel/compat-data" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz#7d19da92c7e0cd8d11c09af2ce1b8e7512a6e723" - integrity sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-member-expression-to-functions" "^7.24.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.24.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.24.5" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" - integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.25.9" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" - integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - regexpu-core "^5.3.1" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.25.9": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0" - integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - regexpu-core "^6.2.0" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" - integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - -"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.23.0", "@babel/helper-member-expression-to-functions@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz#5981e131d5c7003c7d1fa1ad49e86c9b097ec475" - integrity sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" - integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== - dependencies: - "@babel/types" "^7.24.0" - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" - integrity sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.24.3" - "@babel/helper-simple-access" "^7.24.5" - "@babel/helper-split-export-declaration" "^7.24.5" - "@babel/helper-validator-identifier" "^7.24.5" - -"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" - integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== - -"@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" - integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== - -"@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" - integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-wrap-function" "^7.22.20" - -"@babel/helper-remap-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" - integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-wrap-function" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-replace-supers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" - integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" - -"@babel/helper-replace-supers@^7.25.9": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" - integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.26.5" - -"@babel/helper-simple-access@^7.22.5", "@babel/helper-simple-access@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba" - integrity sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-split-export-declaration@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" - integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-string-parser@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" - integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== - -"@babel/helper-validator-identifier@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" - integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helper-wrap-function@^7.22.20": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz#335f934c0962e2c1ed1fb9d79e06a56115067c09" - integrity sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw== - dependencies: - "@babel/helper-function-name" "^7.23.0" - "@babel/template" "^7.24.0" - "@babel/types" "^7.24.5" - -"@babel/helper-wrap-function@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" - integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== - dependencies: - "@babel/template" "^7.25.9" - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helpers@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" - integrity sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q== - dependencies: - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.5" - "@babel/types" "^7.24.5" - -"@babel/helpers@^7.26.7": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4" - integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A== - dependencies: - "@babel/template" "^7.25.9" - "@babel/types" "^7.26.7" - -"@babel/highlight@^7.24.2": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" - integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.5" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.24.7", "@babel/parser@^7.25.3", "@babel/parser@^7.25.9", "@babel/parser@^7.26.5", "@babel/parser@^7.26.7": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.7.tgz#e114cd099e5f7d17b05368678da0fb9f69b3385c" - integrity sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w== - dependencies: - "@babel/types" "^7.26.7" - -"@babel/parser@^7.24.0", "@babel/parser@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" - integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" - integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" - integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" - integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" - integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" - integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-proposal-async-generator-functions@^7.0.0": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" - integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.18.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-export-default-from@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.1.tgz#d242019488277c9a5a8035e5b70de54402644b89" - integrity sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-export-default-from" "^7.24.1" - -"@babel/plugin-proposal-export-default-from@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz#52702be6ef8367fc8f18b8438278332beeb8f87c" - integrity sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" - integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.20.0": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" - integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== - dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.7" - -"@babel/plugin-proposal-optional-catch-binding@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" - integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.20.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" - integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-default-from@^7.0.0", "@babel/plugin-syntax-export-default-from@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.1.tgz#a92852e694910ae4295e6e51e87b83507ed5e6e8" - integrity sha512-cNXSxv9eTkGUtd0PsNMK8Yx5xeScxfpWOUAxE+ZPAXXEcAMOC3fk7LRdXq5fvpra2pLx2p1YtkAhpUbB2SwaRA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-syntax-export-default-from@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.25.9.tgz#86614767a9ff140366f0c3766ef218beb32a730a" - integrity sha512-9MhJ/SMTsVqsd69GyQg89lYR4o9T+oDGv5F6IsigxxqFVOyR/IflDLYP8WDI1l8fkhNGGktqkvL5qwNCtGEpgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz#875c25e3428d7896c87589765fc8b9d32f24bd8d" - integrity sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-syntax-flow@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz#96507595c21b45fccfc2bc758d5c45452e6164fa" - integrity sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-assertions@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" - integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" - integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.23.3": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" - integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-syntax-jsx@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" - integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.0.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.0.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" - integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-syntax-typescript@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" - integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" - integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-arrow-functions@^7.24.7", "@babel/plugin-transform-arrow-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" - integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-async-generator-functions@^7.25.4", "@babel/plugin-transform-async-generator-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz#1b18530b077d18a407c494eb3d1d72da505283a2" - integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-remap-async-to-generator" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-async-to-generator@^7.20.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" - integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== - dependencies: - "@babel/helper-module-imports" "^7.24.1" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-remap-async-to-generator" "^7.22.20" - -"@babel/plugin-transform-async-to-generator@^7.24.7", "@babel/plugin-transform-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" - integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-remap-async-to-generator" "^7.25.9" - -"@babel/plugin-transform-block-scoped-functions@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e" - integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-block-scoping@^7.0.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz#89574191397f85661d6f748d4b89ee4d9ee69a2a" - integrity sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.5" - -"@babel/plugin-transform-block-scoping@^7.25.0", "@babel/plugin-transform-block-scoping@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" - integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-class-properties@^7.24.7", "@babel/plugin-transform-class-properties@^7.25.4", "@babel/plugin-transform-class-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" - integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-class-static-block@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" - integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-classes@^7.0.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz#05e04a09df49a46348299a0e24bfd7e901129339" - integrity sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-plugin-utils" "^7.24.5" - "@babel/helper-replace-supers" "^7.24.1" - "@babel/helper-split-export-declaration" "^7.24.5" - globals "^11.1.0" - -"@babel/plugin-transform-classes@^7.25.4", "@babel/plugin-transform-classes@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" - integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - "@babel/traverse" "^7.25.9" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" - integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/template" "^7.24.0" - -"@babel/plugin-transform-computed-properties@^7.24.7", "@babel/plugin-transform-computed-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" - integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/template" "^7.25.9" - -"@babel/plugin-transform-destructuring@^7.20.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz#80843ee6a520f7362686d1a97a7b53544ede453c" - integrity sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.5" - -"@babel/plugin-transform-destructuring@^7.24.8", "@babel/plugin-transform-destructuring@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" - integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-dotall-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" - integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-duplicate-keys@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" - integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" - integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-dynamic-import@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" - integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-exponentiation-operator@^7.26.3": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" - integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-export-namespace-from@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" - integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-flow-strip-types@^7.20.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz#fa8d0a146506ea195da1671d38eed459242b2dcc" - integrity sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-flow" "^7.24.1" - -"@babel/plugin-transform-flow-strip-types@^7.25.2", "@babel/plugin-transform-flow-strip-types@^7.25.9": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz#2904c85a814e7abb1f4850b8baf4f07d0a2389d4" - integrity sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/plugin-syntax-flow" "^7.26.0" - -"@babel/plugin-transform-for-of@^7.24.7", "@babel/plugin-transform-for-of@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755" - integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-function-name@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" - integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== - dependencies: - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-function-name@^7.25.1", "@babel/plugin-transform-function-name@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" - integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== - dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-json-strings@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" - integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-literals@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" - integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-literals@^7.25.2", "@babel/plugin-transform-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" - integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-logical-assignment-operators@^7.24.7", "@babel/plugin-transform-logical-assignment-operators@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" - integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-member-expression-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" - integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-amd@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" - integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-commonjs@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" - integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== - dependencies: - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-simple-access" "^7.22.5" - -"@babel/plugin-transform-modules-commonjs@^7.24.7", "@babel/plugin-transform-modules-commonjs@^7.24.8", "@babel/plugin-transform-modules-commonjs@^7.25.9", "@babel/plugin-transform-modules-commonjs@^7.26.3": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" - integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== - dependencies: - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-systemjs@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" - integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-modules-umd@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" - integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.0.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" - integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" - integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-new-target@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" - integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator@^7.26.6": - version "7.26.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe" - integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-numeric-separator@^7.24.7", "@babel/plugin-transform-numeric-separator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" - integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-object-rest-spread@^7.24.7", "@babel/plugin-transform-object-rest-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" - integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== - dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - -"@babel/plugin-transform-object-super@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" - integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - -"@babel/plugin-transform-optional-catch-binding@^7.24.7", "@babel/plugin-transform-optional-catch-binding@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" - integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" - integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz#5c3b23f3a6b8fed090f9b98f2926896d3153cc62" - integrity sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.5" - -"@babel/plugin-transform-parameters@^7.24.7", "@babel/plugin-transform-parameters@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" - integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-private-methods@^7.24.7", "@babel/plugin-transform-private-methods@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-private-property-in-object@^7.24.7", "@babel/plugin-transform-private-property-in-object@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" - integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-property-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" - integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-react-display-name@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz#554e3e1a25d181f040cf698b93fd289a03bfdcdb" - integrity sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-react-display-name@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz#4b79746b59efa1f38c8695065a92a9f5afb24f7d" - integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-react-jsx-self@^7.0.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz#22cc7572947895c8e4cd034462e65d8ecf857756" - integrity sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w== - dependencies: - "@babel/helper-plugin-utils" "^7.24.5" - -"@babel/plugin-transform-react-jsx-self@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz#c0b6cae9c1b73967f7f9eb2fca9536ba2fad2858" - integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-react-jsx-source@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz#a2dedb12b09532846721b5df99e52ef8dc3351d0" - integrity sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-react-jsx-source@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz#4c6b8daa520b5f155b5fb55547d7c9fa91417503" - integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-react-jsx@^7.0.0": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" - integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.23.3" - "@babel/types" "^7.23.4" - -"@babel/plugin-transform-react-jsx@^7.25.2": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" - integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-syntax-jsx" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/plugin-transform-regenerator@^7.24.7", "@babel/plugin-transform-regenerator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" - integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - regenerator-transform "^0.15.2" - -"@babel/plugin-transform-regexp-modifiers@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" - integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-reserved-words@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" - integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-runtime@^7.0.0": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz#dc58ad4a31810a890550365cc922e1ff5acb5d7f" - integrity sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ== - dependencies: - "@babel/helper-module-imports" "^7.24.3" - "@babel/helper-plugin-utils" "^7.24.0" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.1" - babel-plugin-polyfill-regenerator "^0.6.1" - semver "^6.3.1" - -"@babel/plugin-transform-runtime@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz#62723ea3f5b31ffbe676da9d6dae17138ae580ea" - integrity sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.6" - babel-plugin-polyfill-regenerator "^0.6.1" - semver "^6.3.1" - -"@babel/plugin-transform-shorthand-properties@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" - integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-shorthand-properties@^7.24.7", "@babel/plugin-transform-shorthand-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" - integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-spread@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" - integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - -"@babel/plugin-transform-spread@^7.24.7", "@babel/plugin-transform-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" - integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-sticky-regex@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" - integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-sticky-regex@^7.24.7", "@babel/plugin-transform-sticky-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" - integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-template-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1" - integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-typeof-symbol@^7.26.7": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz#d0e33acd9223744c1e857dbd6fa17bd0a3786937" - integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-typescript@^7.25.2", "@babel/plugin-transform-typescript@^7.25.9": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.7.tgz#64339515ea3eff610160f62499c3ef437d0ac83d" - integrity sha512-5cJurntg+AT+cgelGP9Bt788DKiAw9gIMSMU2NJrLAilnj0m8WZWUNZPSLOmadYsujHutpgElO+50foX+ib/Wg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-syntax-typescript" "^7.25.9" - -"@babel/plugin-transform-typescript@^7.5.0": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz#bcba979e462120dc06a75bd34c473a04781931b8" - integrity sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.24.5" - "@babel/helper-plugin-utils" "^7.24.5" - "@babel/plugin-syntax-typescript" "^7.24.1" - -"@babel/plugin-transform-unicode-escapes@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" - integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-property-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" - integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-regex@^7.0.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" - integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-unicode-regex@^7.24.7", "@babel/plugin-transform-unicode-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" - integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-sets-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" - integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/preset-env@^7.25.3": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.7.tgz#24d38e211f4570b8d806337035cc3ae798e0c36d" - integrity sha512-Ycg2tnXwixaXOVb29rana8HNPgLVBof8qqtNQ9LE22IoyZboQbGSxI6ZySMdW3K5nAe6gu35IaJefUJflhUFTQ== - dependencies: - "@babel/compat-data" "^7.26.5" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.26.0" - "@babel/plugin-syntax-import-attributes" "^7.26.0" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.25.9" - "@babel/plugin-transform-async-generator-functions" "^7.25.9" - "@babel/plugin-transform-async-to-generator" "^7.25.9" - "@babel/plugin-transform-block-scoped-functions" "^7.26.5" - "@babel/plugin-transform-block-scoping" "^7.25.9" - "@babel/plugin-transform-class-properties" "^7.25.9" - "@babel/plugin-transform-class-static-block" "^7.26.0" - "@babel/plugin-transform-classes" "^7.25.9" - "@babel/plugin-transform-computed-properties" "^7.25.9" - "@babel/plugin-transform-destructuring" "^7.25.9" - "@babel/plugin-transform-dotall-regex" "^7.25.9" - "@babel/plugin-transform-duplicate-keys" "^7.25.9" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-dynamic-import" "^7.25.9" - "@babel/plugin-transform-exponentiation-operator" "^7.26.3" - "@babel/plugin-transform-export-namespace-from" "^7.25.9" - "@babel/plugin-transform-for-of" "^7.25.9" - "@babel/plugin-transform-function-name" "^7.25.9" - "@babel/plugin-transform-json-strings" "^7.25.9" - "@babel/plugin-transform-literals" "^7.25.9" - "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" - "@babel/plugin-transform-member-expression-literals" "^7.25.9" - "@babel/plugin-transform-modules-amd" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.26.3" - "@babel/plugin-transform-modules-systemjs" "^7.25.9" - "@babel/plugin-transform-modules-umd" "^7.25.9" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-new-target" "^7.25.9" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6" - "@babel/plugin-transform-numeric-separator" "^7.25.9" - "@babel/plugin-transform-object-rest-spread" "^7.25.9" - "@babel/plugin-transform-object-super" "^7.25.9" - "@babel/plugin-transform-optional-catch-binding" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - "@babel/plugin-transform-private-methods" "^7.25.9" - "@babel/plugin-transform-private-property-in-object" "^7.25.9" - "@babel/plugin-transform-property-literals" "^7.25.9" - "@babel/plugin-transform-regenerator" "^7.25.9" - "@babel/plugin-transform-regexp-modifiers" "^7.26.0" - "@babel/plugin-transform-reserved-words" "^7.25.9" - "@babel/plugin-transform-shorthand-properties" "^7.25.9" - "@babel/plugin-transform-spread" "^7.25.9" - "@babel/plugin-transform-sticky-regex" "^7.25.9" - "@babel/plugin-transform-template-literals" "^7.25.9" - "@babel/plugin-transform-typeof-symbol" "^7.26.7" - "@babel/plugin-transform-unicode-escapes" "^7.25.9" - "@babel/plugin-transform-unicode-property-regex" "^7.25.9" - "@babel/plugin-transform-unicode-regex" "^7.25.9" - "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.6" - babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.38.1" - semver "^6.3.1" - -"@babel/preset-flow@^7.24.7": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.25.9.tgz#ef8b5e7e3f24a42b3711e77fb14919b87dffed0a" - integrity sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-transform-flow-strip-types" "^7.25.9" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/preset-typescript@^7.24.7": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" - integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-syntax-jsx" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.25.9" - "@babel/plugin-transform-typescript" "^7.25.9" - -"@babel/register@^7.24.6": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.25.9.tgz#1c465acf7dc983d70ccc318eb5b887ecb04f021b" - integrity sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA== - dependencies: - clone-deep "^4.0.1" - find-cache-dir "^2.0.0" - make-dir "^2.1.0" - pirates "^4.0.6" - source-map-support "^0.5.16" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== - -"@babel/runtime@^7.25.0": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.7.tgz#f4e7fe527cd710f8dc0618610b61b4b060c3c341" - integrity sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.8.4": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" - integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.0.0", "@babel/template@^7.22.15", "@babel/template@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/template@^7.25.0", "@babel/template@^7.25.9", "@babel/template@^7.3.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" - integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb" - integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.5" - "@babel/parser" "^7.26.7" - "@babel/template" "^7.25.9" - "@babel/types" "^7.26.7" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" - integrity sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA== - dependencies: - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.24.5" - "@babel/parser" "^7.24.5" - "@babel/types" "^7.24.5" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.7": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb" - integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.5" - "@babel/parser" "^7.26.7" - "@babel/template" "^7.25.9" - "@babel/types" "^7.26.7" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.25.9", "@babel/types@^7.26.5", "@babel/types@^7.26.7", "@babel/types@^7.3.3": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.7.tgz#5e2b89c0768e874d4d061961f3a5a153d71dc17a" - integrity sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.4.4": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" - integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ== - dependencies: - "@babel/helper-string-parser" "^7.24.1" - "@babel/helper-validator-identifier" "^7.24.5" - to-fast-properties "^2.0.0" - -"@emotion/is-prop-valid@1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" - integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== - dependencies: - "@emotion/memoize" "^0.8.1" - -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== - -"@emotion/unitless@0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== - -"@eslint-community/eslint-utils@^4.2.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/eslint-utils@^4.4.0": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" - integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" - integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== - -"@hapi/topo@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@isaacs/ttlcache@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2" - integrity sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/create-cache-key-function@^29.6.3": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" - integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== - dependencies: - "@jest/types" "^29.6.3" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": - version "5.1.1-v1" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== - dependencies: - eslint-scope "5.1.1" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@react-native-async-storage/async-storage@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-2.1.0.tgz#84ca82af320c16d3d8e617508ea523fe786b6781" - integrity sha512-eAGQGPTAuFNEoIQSB5j2Jh1zm5NPyBRTfjRMfCN0W1OakC5WIB5vsDyIQhUweKN9XOE2/V07lqTMGsL0dGXNkA== - dependencies: - merge-options "^3.0.4" - -"@react-native-community/cli-clean@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-15.0.1.tgz#80ce09ffe0d62bb265447007f24dc8dcbf8fe7d3" - integrity sha512-flGTfT005UZvW2LAXVowZ/7ri22oiiZE4pPgMvc8klRxO5uofKIRuohgiHybHtiCo/HNqIz45JmZJvuFrhc4Ow== - dependencies: - "@react-native-community/cli-tools" "15.0.1" - chalk "^4.1.2" - execa "^5.0.0" - fast-glob "^3.3.2" - -"@react-native-community/cli-config-apple@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-config-apple/-/cli-config-apple-15.0.1.tgz#2d845599eada1b479df6716a25dc871c3d202f38" - integrity sha512-GEHUx4NRp9W9or6vygn0TgNeFkcJdNjrtko0vQEJAS4gJdWqP/9LqqwJNlUfaW5jHBN7TKALAMlfRmI12Op3sg== - dependencies: - "@react-native-community/cli-tools" "15.0.1" - chalk "^4.1.2" - execa "^5.0.0" - fast-glob "^3.3.2" - -"@react-native-community/cli-config@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-15.0.1.tgz#fe44472757ebca4348fe4861ceaf9d4daff26767" - integrity sha512-SL3/9zIyzQQPKWei0+W1gNHxCPurrxqpODUWnVLoP38DNcvYCGtsRayw/4DsXgprZfBC+FsscNpd3IDJrG59XA== - dependencies: - "@react-native-community/cli-tools" "15.0.1" - chalk "^4.1.2" - cosmiconfig "^9.0.0" - deepmerge "^4.3.0" - fast-glob "^3.3.2" - joi "^17.2.1" - -"@react-native-community/cli-debugger-ui@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-15.0.1.tgz#bed0d7af5ecb05222bdb7d6e74e21326a583bcf1" - integrity sha512-xkT2TLS8zg5r7Vl9l/2f7JVUoFECnVBS+B5ivrSu2PNZhKkr9lRmJFxC9aVLFb5lIxQQKNDvEyiIDNfP7wjJiA== - dependencies: - serve-static "^1.13.1" - -"@react-native-community/cli-doctor@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-15.0.1.tgz#63cc42e7302f2bfa3739b29fea57b68d5d68fa03" - integrity sha512-YCu44lZR3zZxJJYVTqYZFz9cT9KBfbKI4q2MnKOvkamt00XY3usooMqfuwBAdvM/yvpx7M5w8kbM/nPyj4YCvQ== - dependencies: - "@react-native-community/cli-config" "15.0.1" - "@react-native-community/cli-platform-android" "15.0.1" - "@react-native-community/cli-platform-apple" "15.0.1" - "@react-native-community/cli-platform-ios" "15.0.1" - "@react-native-community/cli-tools" "15.0.1" - chalk "^4.1.2" - command-exists "^1.2.8" - deepmerge "^4.3.0" - envinfo "^7.13.0" - execa "^5.0.0" - node-stream-zip "^1.9.1" - ora "^5.4.1" - semver "^7.5.2" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - yaml "^2.2.1" - -"@react-native-community/cli-platform-android@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-15.0.1.tgz#9706fe454d0e2af4680c3ea1937830c93041a35f" - integrity sha512-QlAMomj6H6TY6pHwjTYMsHDQLP5eLzjAmyW1qb03w/kyS/72elK2bjsklNWJrscFY9TMQLqw7qoAsXf1m5t/dg== - dependencies: - "@react-native-community/cli-tools" "15.0.1" - chalk "^4.1.2" - execa "^5.0.0" - fast-glob "^3.3.2" - fast-xml-parser "^4.4.1" - logkitty "^0.7.1" - -"@react-native-community/cli-platform-apple@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-apple/-/cli-platform-apple-15.0.1.tgz#af3c9bc910c96e823a488c21e7d68a9b4a07c8d1" - integrity sha512-iQj1Dt2fr/Q7X2CQhyhWnece3eLDCark1osfiwpViksOfTH2WdpNS3lIwlFcIKhsieFU7YYwbNuFqQ3tF9Dlvw== - dependencies: - "@react-native-community/cli-config-apple" "15.0.1" - "@react-native-community/cli-tools" "15.0.1" - chalk "^4.1.2" - execa "^5.0.0" - fast-xml-parser "^4.4.1" - -"@react-native-community/cli-platform-ios@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-15.0.1.tgz#a1cb78c3d43b9c2bbb411a074ef11364f2a94bbf" - integrity sha512-6pKzXEIgGL20eE1uOn8iSsNBlMzO1LG+pQOk+7mvD172EPhKm/lRzUVDX5gO/2jvsGoNw6VUW0JX1FI2firwqA== - dependencies: - "@react-native-community/cli-platform-apple" "15.0.1" - -"@react-native-community/cli-server-api@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-15.0.1.tgz#e7975e7638343248835fd379803d557c0ae24d75" - integrity sha512-f3rb3t1ELLaMSX5/LWO/IykglBIgiP3+pPnyl8GphHnBpf3bdIcp7fHlHLemvHE06YxT2nANRxRPjy1gNskenA== - dependencies: - "@react-native-community/cli-debugger-ui" "15.0.1" - "@react-native-community/cli-tools" "15.0.1" - compression "^1.7.1" - connect "^3.6.5" - errorhandler "^1.5.1" - nocache "^3.0.1" - pretty-format "^26.6.2" - serve-static "^1.13.1" - ws "^6.2.3" - -"@react-native-community/cli-tools@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-15.0.1.tgz#3cc5398da72b5d365eb4a30468ebce2bf37fa591" - integrity sha512-N79A+u/94roanfmNohVcNGu6Xg+0idh63JHZFLC9OJJuZwTifGMLDfSTHZATpR1J7rebozQ5ClcSUePavErnSg== - dependencies: - appdirsjs "^1.2.4" - chalk "^4.1.2" - execa "^5.0.0" - find-up "^5.0.0" - mime "^2.4.1" - open "^6.2.0" - ora "^5.4.1" - prompts "^2.4.2" - semver "^7.5.2" - shell-quote "^1.7.3" - sudo-prompt "^9.0.0" - -"@react-native-community/cli-types@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-15.0.1.tgz#ebdb5bc76ade44b2820174fdcb2a3a05999686ec" - integrity sha512-sWiJ62kkGu2mgYni2dsPxOMBzpwTjNsDH1ubY4mqcNEI9Zmzs0vRwwDUEhYqwNGys9+KpBKoZRrT2PAlhO84xA== - dependencies: - joi "^17.2.1" - -"@react-native-community/cli@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-15.0.1.tgz#d703d55cc6540ce3d29fd2fbf3303bea0ffd96f2" - integrity sha512-xIGPytx2bj5HxFk0c7S25AVuJowHmEFg5LFC9XosKc0TSOjP1r6zGC6OqC/arQV/pNuqmZN2IFnpgJn0Bn+hhQ== - dependencies: - "@react-native-community/cli-clean" "15.0.1" - "@react-native-community/cli-config" "15.0.1" - "@react-native-community/cli-debugger-ui" "15.0.1" - "@react-native-community/cli-doctor" "15.0.1" - "@react-native-community/cli-server-api" "15.0.1" - "@react-native-community/cli-tools" "15.0.1" - "@react-native-community/cli-types" "15.0.1" - chalk "^4.1.2" - commander "^9.4.1" - deepmerge "^4.3.0" - execa "^5.0.0" - find-up "^5.0.0" - fs-extra "^8.1.0" - graceful-fs "^4.1.3" - prompts "^2.4.2" - semver "^7.5.2" - -"@react-native/assets-registry@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.77.0.tgz#15c0d65b386e61d669912dfdb2ddab225b10d5c3" - integrity sha512-Ms4tYYAMScgINAXIhE4riCFJPPL/yltughHS950l0VP5sm5glbimn9n7RFn9Tc8cipX74/ddbk19+ydK2iDMmA== - -"@react-native/babel-plugin-codegen@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.77.0.tgz#8d5111a18328a48762c2909849f23c4894952fee" - integrity sha512-5TYPn1k+jdDOZJU4EVb1kZ0p9TCVICXK3uplRev5Gul57oWesAaiWGZOzfRS3lonWeuR4ij8v8PFfIHOaq0vmA== - dependencies: - "@babel/traverse" "^7.25.3" - "@react-native/codegen" "0.77.0" - -"@react-native/babel-preset@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.77.0.tgz#abf6ca0747a1e44e3184e9fc03ac8d9581f000d2" - integrity sha512-Z4yxE66OvPyQ/iAlaETI1ptRLcDm7Tk6ZLqtCPuUX3AMg+JNgIA86979T4RSk486/JrBUBH5WZe2xjj7eEHXsA== - dependencies: - "@babel/core" "^7.25.2" - "@babel/plugin-proposal-export-default-from" "^7.24.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-default-from" "^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.24.7" - "@babel/plugin-transform-async-generator-functions" "^7.25.4" - "@babel/plugin-transform-async-to-generator" "^7.24.7" - "@babel/plugin-transform-block-scoping" "^7.25.0" - "@babel/plugin-transform-class-properties" "^7.25.4" - "@babel/plugin-transform-classes" "^7.25.4" - "@babel/plugin-transform-computed-properties" "^7.24.7" - "@babel/plugin-transform-destructuring" "^7.24.8" - "@babel/plugin-transform-flow-strip-types" "^7.25.2" - "@babel/plugin-transform-for-of" "^7.24.7" - "@babel/plugin-transform-function-name" "^7.25.1" - "@babel/plugin-transform-literals" "^7.25.2" - "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.8" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" - "@babel/plugin-transform-numeric-separator" "^7.24.7" - "@babel/plugin-transform-object-rest-spread" "^7.24.7" - "@babel/plugin-transform-optional-catch-binding" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.8" - "@babel/plugin-transform-parameters" "^7.24.7" - "@babel/plugin-transform-private-methods" "^7.24.7" - "@babel/plugin-transform-private-property-in-object" "^7.24.7" - "@babel/plugin-transform-react-display-name" "^7.24.7" - "@babel/plugin-transform-react-jsx" "^7.25.2" - "@babel/plugin-transform-react-jsx-self" "^7.24.7" - "@babel/plugin-transform-react-jsx-source" "^7.24.7" - "@babel/plugin-transform-regenerator" "^7.24.7" - "@babel/plugin-transform-runtime" "^7.24.7" - "@babel/plugin-transform-shorthand-properties" "^7.24.7" - "@babel/plugin-transform-spread" "^7.24.7" - "@babel/plugin-transform-sticky-regex" "^7.24.7" - "@babel/plugin-transform-typescript" "^7.25.2" - "@babel/plugin-transform-unicode-regex" "^7.24.7" - "@babel/template" "^7.25.0" - "@react-native/babel-plugin-codegen" "0.77.0" - babel-plugin-syntax-hermes-parser "0.25.1" - babel-plugin-transform-flow-enums "^0.0.2" - react-refresh "^0.14.0" - -"@react-native/codegen@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.77.0.tgz#e735f7ed99705ad7a9d66827cf1f5f127c54a578" - integrity sha512-rE9lXx41ZjvE8cG7e62y/yGqzUpxnSvJ6me6axiX+aDewmI4ZrddvRGYyxCnawxy5dIBHSnrpZse3P87/4Lm7w== - dependencies: - "@babel/parser" "^7.25.3" - glob "^7.1.1" - hermes-parser "0.25.1" - invariant "^2.2.4" - jscodeshift "^17.0.0" - nullthrows "^1.1.1" - yargs "^17.6.2" - -"@react-native/community-cli-plugin@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.77.0.tgz#14af613b7c0c7f9a8a8fb7e07e08b84c38c402cd" - integrity sha512-GRshwhCHhtupa3yyCbel14SlQligV8ffNYN5L1f8HCo2SeGPsBDNjhj2U+JTrMPnoqpwowPGvkCwyqwqYff4MQ== - dependencies: - "@react-native/dev-middleware" "0.77.0" - "@react-native/metro-babel-transformer" "0.77.0" - chalk "^4.0.0" - debug "^2.2.0" - invariant "^2.2.4" - metro "^0.81.0" - metro-config "^0.81.0" - metro-core "^0.81.0" - readline "^1.3.0" - semver "^7.1.3" - -"@react-native/debugger-frontend@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.77.0.tgz#9846c905ea423e3b12d94549268ca0e668ed0e7b" - integrity sha512-glOvSEjCbVXw+KtfiOAmrq21FuLE1VsmBsyT7qud4KWbXP43aUEhzn70mWyFuiIdxnzVPKe2u8iWTQTdJksR1w== - -"@react-native/dev-middleware@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.77.0.tgz#a5a660e2fc9acf2262e0fc68164b26df3527356a" - integrity sha512-DAlEYujm43O+Dq98KP2XfLSX5c/TEGtt+JBDEIOQewk374uYY52HzRb1+Gj6tNaEj/b33no4GibtdxbO5zmPhg== - dependencies: - "@isaacs/ttlcache" "^1.4.1" - "@react-native/debugger-frontend" "0.77.0" - chrome-launcher "^0.15.2" - chromium-edge-launcher "^0.2.0" - connect "^3.6.5" - debug "^2.2.0" - nullthrows "^1.1.1" - open "^7.0.3" - selfsigned "^2.4.1" - serve-static "^1.16.2" - ws "^6.2.3" - -"@react-native/eslint-config@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/eslint-config/-/eslint-config-0.77.0.tgz#2f43c9753ef205dfd115600571cdce09bcf40674" - integrity sha512-azEiJNe/v1MjXE5Cekn8ygV4an0T3mNem4Afmeaq9tO9rfbOYr3VpTMFgc4B42SZgS4S6lyIqvwTfc8bSp0KRw== - dependencies: - "@babel/core" "^7.25.2" - "@babel/eslint-parser" "^7.25.1" - "@react-native/eslint-plugin" "0.77.0" - "@typescript-eslint/eslint-plugin" "^7.1.1" - "@typescript-eslint/parser" "^7.1.1" - eslint-config-prettier "^8.5.0" - eslint-plugin-eslint-comments "^3.2.0" - eslint-plugin-ft-flow "^2.0.1" - eslint-plugin-jest "^27.9.0" - eslint-plugin-react "^7.30.1" - eslint-plugin-react-hooks "^4.6.0" - eslint-plugin-react-native "^4.0.0" - -"@react-native/eslint-plugin@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/eslint-plugin/-/eslint-plugin-0.77.0.tgz#1a59a1899da3b3c4a6f599f589cbf6802c22d70f" - integrity sha512-1DXUDiqsgvFpK633SsOF01aAtWAaI/+KqPJAoZOVdSsodk70wNYyrHpF9rJBXWhyT/peTBE5y2kK2kT/Y7JcQA== - -"@react-native/gradle-plugin@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.77.0.tgz#81e1a382e6c31f4f21e43ade2612c05f3e58e722" - integrity sha512-rmfh93jzbndSq7kihYHUQ/EGHTP8CCd3GDCmg5SbxSOHAaAYx2HZ28ZG7AVcGUsWeXp+e/90zGIyfOzDRx0Zaw== - -"@react-native/js-polyfills@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.77.0.tgz#892d7f2f55c380623d1998a752f83bd37500a941" - integrity sha512-kHFcMJVkGb3ptj3yg1soUsMHATqal4dh0QTGAbYihngJ6zy+TnP65J3GJq4UlwqFE9K1RZkeCmTwlmyPFHOGvA== - -"@react-native/metro-babel-transformer@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.77.0.tgz#86eef50eac7cae5ea54976d0195862dbb62958fb" - integrity sha512-19GfvhBRKCU3UDWwCnDR4QjIzz3B2ZuwhnxMRwfAgPxz7QY9uKour9RGmBAVUk1Wxi/SP7dLEvWnmnuBO39e2A== - dependencies: - "@babel/core" "^7.25.2" - "@react-native/babel-preset" "0.77.0" - hermes-parser "0.25.1" - nullthrows "^1.1.1" - -"@react-native/metro-config@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/metro-config/-/metro-config-0.77.0.tgz#447f3c06d5714600c1bfb6e872541c39775f8bd9" - integrity sha512-IhcsIDdoIYkXf3FoZxayRGg2oMLBhpqWEH6IDJlJTQamOQ3PUm2uF1e7yzvnatZ18A6JCNhOlxnBK7m5ZWQPYQ== - dependencies: - "@react-native/js-polyfills" "0.77.0" - "@react-native/metro-babel-transformer" "0.77.0" - metro-config "^0.81.0" - metro-runtime "^0.81.0" - -"@react-native/normalize-colors@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.77.0.tgz#dedd55b7c8d9c4b43cd3d12a06b654f0ff97949f" - integrity sha512-qjmxW3xRZe4T0ZBEaXZNHtuUbRgyfybWijf1yUuQwjBt24tSapmIslwhCjpKidA0p93ssPcepquhY0ykH25mew== - -"@react-native/typescript-config@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/typescript-config/-/typescript-config-0.77.0.tgz#3a2c6eb9360f3b3b1c630bb02d9a0ac4081d0c1c" - integrity sha512-WunTrKSQtGKi7gVf24jinHkXXi3tSkChRfrUPFY1njNWwVNtJ/H0ElSlJKUIWaBcd6DKG4ZddKsftWBAWTV0Sg== - -"@react-native/virtualized-lists@0.77.0": - version "0.77.0" - resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.77.0.tgz#a8ac08b0de3f78648a3a8573135755301f36b03d" - integrity sha512-ppPtEu9ISO9iuzpA2HBqrfmDpDAnGGduNDVaegadOzbMCPAB3tC9Blxdu9W68LyYlNQILIsP6/FYtLwf7kfNew== - dependencies: - invariant "^2.2.4" - nullthrows "^1.1.1" - -"@react-navigation/core@^7.3.1": - version "7.3.1" - resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.3.1.tgz#c6d4857fa2dd321d12ca87e200478c38c420f157" - integrity sha512-S3KCGvNsoqVk8ErAtQI2EAhg9185lahF5OY01ofrrD4Ij/uk3QEHHjoGQhR5l5DXSCSKr1JbMQA7MEKMsBiWZA== - dependencies: - "@react-navigation/routers" "^7.1.2" - escape-string-regexp "^4.0.0" - nanoid "3.3.8" - query-string "^7.1.3" - react-is "^18.2.0" - use-latest-callback "^0.2.1" - use-sync-external-store "^1.2.2" - -"@react-navigation/elements@^2.2.5": - version "2.2.5" - resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.2.5.tgz#0e2ca76e2003e96b417a3d7c2829bf1afd69193f" - integrity sha512-sDhE+W14P7MNWLMxXg1MEVXwkLUpMZJGflE6nQNzLmolJQIHgcia0Mrm8uRa3bQovhxYu1UzEojLZ+caoZt7Fg== - dependencies: - color "^4.2.3" - -"@react-navigation/native-stack@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.2.0.tgz#8aa489f88d662b3543a931b9cb934bb2e09a4893" - integrity sha512-mw7Nq9qQrGsmJmCTwIIWB7yY/3tWYXvQswx+HJScGAadIjemvytJXm1fcl3+YZ9T9Ym0aERcVe5kDs+ny3X4vA== - dependencies: - "@react-navigation/elements" "^2.2.5" - warn-once "^0.1.1" - -"@react-navigation/native@^7.0.14": - version "7.0.14" - resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.0.14.tgz#b3ee2879038dcf0523d26516af88d3adc549ce5e" - integrity sha512-Gi6lLw4VOGSWAhmUdJOMauOKGK51/YA1CprjXm91sNfgERWvznqEMw8QmUQx9SEqYfi0LfZhbzpMst09SJ00lw== - dependencies: - "@react-navigation/core" "^7.3.1" - escape-string-regexp "^4.0.0" - fast-deep-equal "^3.1.3" - nanoid "3.3.8" - use-latest-callback "^0.2.1" - -"@react-navigation/routers@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.1.2.tgz#647a63e383673de0c4fc10c64a17f551d5da0a17" - integrity sha512-emdEjpVDK8zbiu2GChC8oYIAub9i/OpNuQJekVsbyFCBz4/TzaBzms38Q53YaNhdIFNmiYLfHv/Y1Ub7KYfm3w== - dependencies: - nanoid "3.3.8" - -"@sideway/address@^4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" - integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" - integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== - dependencies: - "@babel/types" "^7.20.7" - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node-forge@^1.3.0": - version "1.3.11" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" - integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== - dependencies: - "@types/node" "*" - -"@types/node@*": - version "20.12.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.11.tgz#c4ef00d3507000d17690643278a60dc55a9dc9be" - integrity sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw== - dependencies: - undici-types "~5.26.4" - -"@types/react-native-base64@^0.2.0": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@types/react-native-base64/-/react-native-base64-0.2.2.tgz#d4e1d537e6d547d23d96a1e64627acc13587ae6b" - integrity sha512-obr+/L9Jaxdr+xCVS/IQcYgreg5xtnui4Wqw/G1acBUtW2CnqVJj6lK6F/5F3+5d2oZEo5xDDLqy8GVn2HbEmw== - -"@types/semver@^7.3.12": - version "7.5.8" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" - integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== - -"@types/stack-utils@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/stylis@4.2.5": - version "4.2.5" - resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.5.tgz#1daa6456f40959d06157698a653a9ab0a70281df" - integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^15.0.0": - version "15.0.19" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.19.tgz#328fb89e46109ecbdb70c295d96ff2f46dfd01b9" - integrity sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.8": - version "17.0.32" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" - integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^7.1.1": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" - integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.18.0" - "@typescript-eslint/type-utils" "7.18.0" - "@typescript-eslint/utils" "7.18.0" - "@typescript-eslint/visitor-keys" "7.18.0" - graphemer "^1.4.0" - ignore "^5.3.1" - natural-compare "^1.4.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/parser@^7.1.1": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0" - integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== - dependencies: - "@typescript-eslint/scope-manager" "7.18.0" - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/typescript-estree" "7.18.0" - "@typescript-eslint/visitor-keys" "7.18.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/scope-manager@7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz#c928e7a9fc2c0b3ed92ab3112c614d6bd9951c83" - integrity sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA== - dependencies: - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/visitor-keys" "7.18.0" - -"@typescript-eslint/type-utils@7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" - integrity sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA== - dependencies: - "@typescript-eslint/typescript-estree" "7.18.0" - "@typescript-eslint/utils" "7.18.0" - debug "^4.3.4" - ts-api-utils "^1.3.0" - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/types@7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" - integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== - -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz#b5868d486c51ce8f312309ba79bdb9f331b37931" - integrity sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA== - dependencies: - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/visitor-keys" "7.18.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/utils@7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" - integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.18.0" - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/typescript-estree" "7.18.0" - -"@typescript-eslint/utils@^5.10.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz#0564629b6124d67607378d0f0332a0495b25e7d7" - integrity sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg== - dependencies: - "@typescript-eslint/types" "7.18.0" - eslint-visitor-keys "^3.4.3" - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@^1.3.7, accepts@~1.3.7: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn@^8.8.2: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== - -anser@^1.4.9: - version "1.4.10" - resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" - integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== - -ansi-fragments@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-fragments/-/ansi-fragments-0.2.1.tgz#24409c56c4cc37817c3d7caa99d8969e2de5a05e" - integrity sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w== - dependencies: - colorette "^1.0.7" - slice-ansi "^2.0.0" - strip-ansi "^5.0.0" - -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== - -ansi-regex@^5.0.0, ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -appdirsjs@^1.2.4: - version "1.2.7" - resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.7.tgz#50b4b7948a26ba6090d4aede2ae2dc2b051be3b3" - integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-includes@^3.1.6, array-includes@^3.1.7: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.findlast@^1.2.4: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" - integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-shim-unscopables "^1.0.2" - -array.prototype.flat@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.toreversed@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" - integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.tosorted@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" - integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.1.0" - es-shim-unscopables "^1.0.2" - -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - -asap@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - -ast-types@^0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.16.1.tgz#7a9da1617c9081bc121faafe91711b4c8bb81da2" - integrity sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg== - dependencies: - tslib "^2.0.1" - -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-module-resolver@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz#cdeac5d4aaa3b08dd1ac23ddbf516660ed2d293e" - integrity sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg== - dependencies: - find-babel-config "^2.1.1" - glob "^9.3.3" - pkg-up "^3.1.0" - reselect "^4.1.7" - resolve "^1.22.8" - -babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.11" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" - integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.2" - semver "^6.3.1" - -babel-plugin-polyfill-corejs3@^0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" - integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.1" - core-js-compat "^3.36.1" - -babel-plugin-polyfill-corejs3@^0.10.6: - version "0.10.6" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7" - integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" - core-js-compat "^3.38.0" - -babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" - integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" - -babel-plugin-syntax-hermes-parser@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz#58b539df973427fcfbb5176a3aec7e5dee793cb0" - integrity sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ== - dependencies: - hermes-parser "0.25.1" - -babel-plugin-transform-flow-enums@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz#d1d0cc9bdc799c850ca110d0ddc9f21b9ec3ef25" - integrity sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ== - dependencies: - "@babel/plugin-syntax-flow" "^7.12.1" - -babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1, base64-js@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.22.2, browserslist@^4.23.0: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== - dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -browserslist@^4.24.0, browserslist@^4.24.3: - version "4.24.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" - integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== - dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" - update-browserslist-db "^1.1.1" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -camelize@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" - integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== - -caniuse-lite@^1.0.30001587: - version "1.0.30001617" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz#809bc25f3f5027ceb33142a7d6c40759d7a901eb" - integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA== - -caniuse-lite@^1.0.30001688: - version "1.0.30001695" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz#39dfedd8f94851132795fdf9b79d29659ad9c4d4" - integrity sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chrome-launcher@^0.15.2: - version "0.15.2" - resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz#4e6404e32200095fdce7f6a1e1004f9bd36fa5da" - integrity sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ== - dependencies: - "@types/node" "*" - escape-string-regexp "^4.0.0" - is-wsl "^2.2.0" - lighthouse-logger "^1.0.0" - -chromium-edge-launcher@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz#0c378f28c99aefc360705fa155de0113997f62fc" - integrity sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg== - dependencies: - "@types/node" "*" - escape-string-regexp "^4.0.0" - is-wsl "^2.2.0" - lighthouse-logger "^1.0.0" - mkdirp "^1.0.4" - rimraf "^3.0.2" - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.5.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - -colorette@^1.0.7: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" - integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== - -command-exists@^1.2.8: - version "1.2.9" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" - integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== - -commander@^12.0.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^9.4.1: - version "9.5.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" - integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -compressible@~2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.1: - version "1.7.5" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.5.tgz#fdd256c0a642e39e314c478f6c2cd654edd74c93" - integrity sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q== - dependencies: - bytes "3.1.2" - compressible "~2.0.18" - debug "2.6.9" - negotiator "~0.6.4" - on-headers "~1.0.2" - safe-buffer "5.2.1" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -connect@^3.6.5: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-js-compat@^3.36.1: - version "3.37.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.0.tgz#d9570e544163779bb4dff1031c7972f44918dc73" - integrity sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA== - dependencies: - browserslist "^4.23.0" - -core-js-compat@^3.38.0, core-js-compat@^3.38.1: - version "3.40.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38" - integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ== - dependencies: - browserslist "^4.24.3" - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^5.0.5: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -cosmiconfig@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -css-color-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" - integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== - -css-to-react-native@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" - integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== - dependencies: - camelize "^1.0.0" - css-color-keywords "^1.0.0" - postcss-value-parser "^4.0.2" - -csstype@3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -dayjs@^1.8.15: - version "1.11.13" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" - integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== - -debug@2.6.9, debug@^2.2.0, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decode-uri-component@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -deepmerge@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -denodeify@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" - integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.668: - version "1.4.763" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.763.tgz#64f2041ed496fd6fc710b9be806fe91da9334f91" - integrity sha512-k4J8NrtJ9QrvHLRo8Q18OncqBCB7tIUyqxRcJnlonQ0ioHKYB988GcDFF3ZePmnb8eHEopDs/wPHR/iGAFgoUQ== - -electron-to-chromium@^1.5.73: - version "1.5.88" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.88.tgz#cdb6e2dda85e6521e8d7d3035ba391c8848e073a" - integrity sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -env-paths@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -envinfo@^7.13.0: - version "7.14.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae" - integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -error-stack-parser@^2.0.6: - version "2.1.4" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" - integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== - dependencies: - stackframe "^1.3.4" - -errorhandler@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/errorhandler/-/errorhandler-1.5.1.tgz#b9ba5d17cf90744cd1e851357a6e75bf806a9a91" - integrity sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A== - dependencies: - accepts "~1.3.7" - escape-html "~1.0.3" - -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-iterator-helpers@^1.0.17: - version "1.0.19" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" - integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.3" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - iterator.prototype "^1.1.2" - safe-array-concat "^1.1.2" - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== - dependencies: - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1, escalade@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-prettier@^8.5.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" - integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== - -eslint-plugin-eslint-comments@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa" - integrity sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ== - dependencies: - escape-string-regexp "^1.0.5" - ignore "^5.0.5" - -eslint-plugin-ft-flow@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz#3b3c113c41902bcbacf0e22b536debcfc3c819e8" - integrity sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg== - dependencies: - lodash "^4.17.21" - string-natural-compare "^3.0.1" - -eslint-plugin-jest@^27.9.0: - version "27.9.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz#7c98a33605e1d8b8442ace092b60e9919730000b" - integrity sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug== - dependencies: - "@typescript-eslint/utils" "^5.10.0" - -eslint-plugin-react-hooks@^4.6.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" - integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== - -eslint-plugin-react-native-globals@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" - integrity sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g== - -eslint-plugin-react-native@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz#5343acd3b2246bc1b857ac38be708f070d18809f" - integrity sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q== - dependencies: - eslint-plugin-react-native-globals "^0.1.1" - -eslint-plugin-react@^7.30.1: - version "7.34.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" - integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== - dependencies: - array-includes "^3.1.7" - array.prototype.findlast "^1.2.4" - array.prototype.flatmap "^1.3.2" - array.prototype.toreversed "^1.1.2" - array.prototype.tosorted "^1.1.3" - doctrine "^2.1.0" - es-iterator-helpers "^1.0.17" - estraverse "^5.3.0" - jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" - object.hasown "^1.1.3" - object.values "^1.1.7" - prop-types "^15.8.1" - resolve "^2.0.0-next.5" - semver "^6.3.1" - string.prototype.matchall "^4.0.10" - -eslint-scope@5.1.1, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-visitor-keys@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -esprima@^4.0.0, esprima@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0, estraverse@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -event-target-shim@^5.0.0, event-target-shim@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exponential-backoff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" - integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== - -fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-xml-parser@^4.4.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz#a7e665ff79b7919100a5202f23984b6150f9b31e" - integrity sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w== - dependencies: - strnum "^1.0.5" - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - 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" - -find-babel-config@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-2.1.1.tgz#93703fc8e068db5e4c57592900c5715dd04b7e5b" - integrity sha512-5Ji+EAysHGe1OipH7GN4qDjok5Z1uw5KAwDCbicU/4wyTZY7CqOCzcWbG7J5ad9mazq67k89fXlbc1MuIfl9uA== - dependencies: - json5 "^2.2.3" - path-exists "^4.0.0" - -find-cache-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flow-enums-runtime@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787" - integrity sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw== - -flow-parser@0.*: - version "0.236.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.236.0.tgz#8e8e6c59ff7e8d196c0ed215b3919320a1c6e332" - integrity sha512-0OEk9Gr+Yj7wjDW2KgaNYUypKau71jAfFyeLQF5iVtxqc6uJHag/MT7pmaEApf4qM7u86DkBcd4ualddYMfbLw== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^9.3.3: - version "9.3.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" - integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== - dependencies: - fs.realpath "^1.0.0" - minimatch "^8.0.2" - minipass "^4.2.4" - path-scurry "^1.6.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globalthis@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -hermes-estree@0.24.0: - version "0.24.0" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.24.0.tgz#487dc1ddc0bae698c2d79f34153ac9bf62d7b3c0" - integrity sha512-LyoXLB7IFzeZW0EvAbGZacbxBN7t6KKSDqFJPo3Ydow7wDlrDjXwsdiAHV6XOdvEN9MEuWXsSIFN4tzpyrXIHw== - -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - -hermes-parser@0.24.0: - version "0.24.0" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.24.0.tgz#2ed19d079efc0848eb1f800f0c393a074c4696fb" - integrity sha512-IJooSvvu2qNRe7oo9Rb04sUT4omtZqZqf9uq9WM25Tb6v3usmvA93UqfnnoWs5V0uYjEl9Al6MNU10MCGKLwpg== - dependencies: - hermes-estree "0.24.0" - -hermes-parser@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.0.5, ignore@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== - -ignore@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -image-size@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.1.1.tgz#ddd67d4dc340e52ac29ce5f546a09f4e29e840ac" - integrity sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ== - dependencies: - queue "6.0.2" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== - dependencies: - has-tostringtag "^1.0.0" - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1, is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-finalizationregistry@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== - dependencies: - call-bind "^1.0.2" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - -is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-map@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== - -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-set@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-weakmap@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" - integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== - -is-wsl@^2.1.1, is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -iterator.prototype@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" - integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== - dependencies: - define-properties "^1.2.1" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - reflect.getprototypeof "^1.0.4" - set-function-name "^2.0.1" - -jest-environment-node@^29.6.3: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.6.3: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-worker@^29.6.3, jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -joi@^17.2.1: - version "17.13.3" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" - integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== - 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@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsc-android@^250231.0.0: - version "250231.0.0" - resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250231.0.0.tgz#91720f8df382a108872fa4b3f558f33ba5e95262" - integrity sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw== - -jsc-safe-url@^0.2.2: - version "0.2.4" - resolved "https://registry.yarnpkg.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz#141c14fbb43791e88d5dc64e85a374575a83477a" - integrity sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q== - -jscodeshift@^17.0.0: - version "17.1.2" - resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-17.1.2.tgz#d77e9d3d08fdbb1548818bc22f653aba7fc21a25" - integrity sha512-uime4vFOiZ1o3ICT4Sm/AbItHEVw2oCxQ3a0egYVy3JMMOctxe07H3SKL1v175YqjMt27jn1N+3+Bj9SKDNgdQ== - dependencies: - "@babel/core" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/plugin-transform-class-properties" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.7" - "@babel/plugin-transform-private-methods" "^7.24.7" - "@babel/preset-flow" "^7.24.7" - "@babel/preset-typescript" "^7.24.7" - "@babel/register" "^7.24.6" - flow-parser "0.*" - graceful-fs "^4.2.4" - micromatch "^4.0.7" - neo-async "^2.5.0" - picocolors "^1.0.1" - recast "^0.23.9" - tmp "^0.2.3" - write-file-atomic "^5.0.1" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -jsesc@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -"jsx-ast-utils@^2.4.1 || ^3.0.0": - version "3.3.5" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" - integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - object.assign "^4.1.4" - object.values "^1.1.6" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -lighthouse-logger@^1.0.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa" - integrity sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g== - dependencies: - debug "^2.6.9" - marky "^1.2.2" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.throttle@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" - integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== - -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -logkitty@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/logkitty/-/logkitty-0.7.1.tgz#8e8d62f4085a826e8d38987722570234e33c6aa7" - integrity sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ== - dependencies: - ansi-fragments "^0.2.1" - dayjs "^1.8.15" - yargs "^15.1.0" - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lru-cache@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" - integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -make-dir@^2.0.0, make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -marky@^1.2.2: - version "1.2.5" - resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" - integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== - -memoize-one@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" - integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== - -merge-options@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" - integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== - dependencies: - is-plain-obj "^2.1.0" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -metro-babel-transformer@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.81.0.tgz#cf468eafea52e4d8a77844eb7257f8a76e9d9d94" - integrity sha512-Dc0QWK4wZIeHnyZ3sevWGTnnSkIDDn/SWyfrn99zbKbDOCoCYy71PAn9uCRrP/hduKLJQOy+tebd63Rr9D8tXg== - dependencies: - "@babel/core" "^7.25.2" - flow-enums-runtime "^0.0.6" - hermes-parser "0.24.0" - nullthrows "^1.1.1" - -metro-cache-key@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.81.0.tgz#5db34fa1a323a2310205bda7abd0df9614e36f45" - integrity sha512-qX/IwtknP9bQZL78OK9xeSvLM/xlGfrs6SlUGgHvrxtmGTRSsxcyqxR+c+7ch1xr05n62Gin/O44QKg5V70rNQ== - dependencies: - flow-enums-runtime "^0.0.6" - -metro-cache@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.81.0.tgz#90470d10d190ad708f04c6e337eec2c7cddb3db0" - integrity sha512-DyuqySicHXkHUDZFVJmh0ygxBSx6pCKUrTcSgb884oiscV/ROt1Vhye+x+OIHcsodyA10gzZtrVtxIFV4l9I4g== - dependencies: - exponential-backoff "^3.1.1" - flow-enums-runtime "^0.0.6" - metro-core "0.81.0" - -metro-config@0.81.0, metro-config@^0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.81.0.tgz#8f8074033cb7e9ddb5b0459642adf6880bc9fbc1" - integrity sha512-6CinEaBe3WLpRlKlYXXu8r1UblJhbwD6Gtnoib5U8j6Pjp7XxMG9h/DGMeNp9aGLDu1OieUqiXpFo7O0/rR5Kg== - dependencies: - connect "^3.6.5" - cosmiconfig "^5.0.5" - flow-enums-runtime "^0.0.6" - jest-validate "^29.6.3" - metro "0.81.0" - metro-cache "0.81.0" - metro-core "0.81.0" - metro-runtime "0.81.0" - -metro-core@0.81.0, metro-core@^0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.81.0.tgz#d0b634f9cf97849b7730c59457ab7a439811d4c8" - integrity sha512-CVkM5YCOAFkNMvJai6KzA0RpztzfEKRX62/PFMOJ9J7K0uq/UkOFLxcgpcncMIrfy0PbfEj811b69tjULUQe1Q== - dependencies: - flow-enums-runtime "^0.0.6" - lodash.throttle "^4.1.1" - metro-resolver "0.81.0" - -metro-file-map@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.81.0.tgz#af0ccf4f8db4fd8429f78f231faa49dde2c402c3" - integrity sha512-zMDI5uYhQCyxbye/AuFx/pAbsz9K+vKL7h1ShUXdN2fz4VUPiyQYRsRqOoVG1DsiCgzd5B6LW0YW77NFpjDQeg== - dependencies: - anymatch "^3.0.3" - debug "^2.2.0" - fb-watchman "^2.0.0" - flow-enums-runtime "^0.0.6" - graceful-fs "^4.2.4" - invariant "^2.2.4" - jest-worker "^29.6.3" - micromatch "^4.0.4" - node-abort-controller "^3.1.1" - nullthrows "^1.1.1" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.3.2" - -metro-minify-terser@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.81.0.tgz#8b0abe977d63a99b99fa94d53678ef3170d5b659" - integrity sha512-U2ramh3W822ZR1nfXgIk+emxsf5eZSg10GbQrT0ZizImK8IZ5BmJY+BHRIkQgHzWFpExOVxC7kWbGL1bZALswA== - dependencies: - flow-enums-runtime "^0.0.6" - terser "^5.15.0" - -metro-react-native-babel-preset@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.8.tgz#7476efae14363cbdfeeec403b4f01d7348e6c048" - integrity sha512-Ptza08GgqzxEdK8apYsjTx2S8WDUlS2ilBlu9DR1CUcHmg4g3kOkFylZroogVAUKtpYQNYwAvdsjmrSdDNtiAg== - dependencies: - "@babel/core" "^7.20.0" - "@babel/plugin-proposal-async-generator-functions" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.18.0" - "@babel/plugin-proposal-export-default-from" "^7.0.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" - "@babel/plugin-proposal-numeric-separator" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.20.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" - "@babel/plugin-proposal-optional-chaining" "^7.20.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-export-default-from" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.18.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" - "@babel/plugin-syntax-optional-chaining" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-async-to-generator" "^7.20.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.0.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.20.0" - "@babel/plugin-transform-flow-strip-types" "^7.20.0" - "@babel/plugin-transform-function-name" "^7.0.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" - "@babel/plugin-transform-parameters" "^7.0.0" - "@babel/plugin-transform-react-display-name" "^7.0.0" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/plugin-transform-react-jsx-self" "^7.0.0" - "@babel/plugin-transform-react-jsx-source" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-typescript" "^7.5.0" - "@babel/plugin-transform-unicode-regex" "^7.0.0" - "@babel/template" "^7.0.0" - babel-plugin-transform-flow-enums "^0.0.2" - react-refresh "^0.4.0" - -metro-resolver@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.81.0.tgz#141f4837e1e0c5a1810ea02f2d9be3c9f6cf3766" - integrity sha512-Uu2Q+buHhm571cEwpPek8egMbdSTqmwT/5U7ZVNpK6Z2ElQBBCxd7HmFAslKXa7wgpTO2FAn6MqGeERbAtVDUA== - dependencies: - flow-enums-runtime "^0.0.6" - -metro-runtime@0.81.0, metro-runtime@^0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.81.0.tgz#63af9b3fec15d1f307d89ef4881f5ba2c592291e" - integrity sha512-6oYB5HOt37RuGz2eV4A6yhcl+PUTwJYLDlY9vhT+aVjbUWI6MdBCf69vc4f5K5Vpt+yOkjy+2LDwLS0ykWFwYw== - dependencies: - "@babel/runtime" "^7.25.0" - flow-enums-runtime "^0.0.6" - -metro-source-map@0.81.0, metro-source-map@^0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.81.0.tgz#ca83964124bb227d5f0bdb1ee304dbfe635f869e" - integrity sha512-TzsVxhH83dyxg4A4+L1nzNO12I7ps5IHLjKGZH3Hrf549eiZivkdjYiq/S5lOB+p2HiQ+Ykcwtmcja95LIC62g== - dependencies: - "@babel/traverse" "^7.25.3" - "@babel/traverse--for-generate-function-map" "npm:@babel/traverse@^7.25.3" - "@babel/types" "^7.25.2" - flow-enums-runtime "^0.0.6" - invariant "^2.2.4" - metro-symbolicate "0.81.0" - nullthrows "^1.1.1" - ob1 "0.81.0" - source-map "^0.5.6" - vlq "^1.0.0" - -metro-symbolicate@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.81.0.tgz#b7b1eae8bfd6ad2a922fa2bcb9f2144e464adafb" - integrity sha512-C/1rWbNTPYp6yzID8IPuQPpVGzJ2rbWYBATxlvQ9dfK5lVNoxcwz77hjcY8ISLsRRR15hyd/zbjCNKPKeNgE1Q== - dependencies: - flow-enums-runtime "^0.0.6" - invariant "^2.2.4" - metro-source-map "0.81.0" - nullthrows "^1.1.1" - source-map "^0.5.6" - through2 "^2.0.1" - vlq "^1.0.0" - -metro-transform-plugins@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.81.0.tgz#614c0e50593df545487b3f3383fed810c608fb32" - integrity sha512-uErLAPBvttGCrmGSCa0dNHlOTk3uJFVEVWa5WDg6tQ79PRmuYRwzUgLhVzn/9/kyr75eUX3QWXN79Jvu4txt6Q== - dependencies: - "@babel/core" "^7.25.2" - "@babel/generator" "^7.25.0" - "@babel/template" "^7.25.0" - "@babel/traverse" "^7.25.3" - flow-enums-runtime "^0.0.6" - nullthrows "^1.1.1" - -metro-transform-worker@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.81.0.tgz#43e63c95014f36786f0e1a132c778c6392950de7" - integrity sha512-HrQ0twiruhKy0yA+9nK5bIe3WQXZcC66PXTvRIos61/EASLAP2DzEmW7IxN/MGsfZegN2UzqL2CG38+mOB45vg== - dependencies: - "@babel/core" "^7.25.2" - "@babel/generator" "^7.25.0" - "@babel/parser" "^7.25.3" - "@babel/types" "^7.25.2" - flow-enums-runtime "^0.0.6" - metro "0.81.0" - metro-babel-transformer "0.81.0" - metro-cache "0.81.0" - metro-cache-key "0.81.0" - metro-minify-terser "0.81.0" - metro-source-map "0.81.0" - metro-transform-plugins "0.81.0" - nullthrows "^1.1.1" - -metro@0.81.0, metro@^0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/metro/-/metro-0.81.0.tgz#cffe9b7d597728dee8b57903ca155417b7c13a4f" - integrity sha512-kzdzmpL0gKhEthZ9aOV7sTqvg6NuTxDV8SIm9pf9sO8VVEbKrQk5DNcwupOUjgPPFAuKUc2NkT0suyT62hm2xg== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/core" "^7.25.2" - "@babel/generator" "^7.25.0" - "@babel/parser" "^7.25.3" - "@babel/template" "^7.25.0" - "@babel/traverse" "^7.25.3" - "@babel/types" "^7.25.2" - accepts "^1.3.7" - chalk "^4.0.0" - ci-info "^2.0.0" - connect "^3.6.5" - debug "^2.2.0" - denodeify "^1.2.1" - error-stack-parser "^2.0.6" - flow-enums-runtime "^0.0.6" - graceful-fs "^4.2.4" - hermes-parser "0.24.0" - image-size "^1.0.2" - invariant "^2.2.4" - jest-worker "^29.6.3" - jsc-safe-url "^0.2.2" - lodash.throttle "^4.1.1" - metro-babel-transformer "0.81.0" - metro-cache "0.81.0" - metro-cache-key "0.81.0" - metro-config "0.81.0" - metro-core "0.81.0" - metro-file-map "0.81.0" - metro-resolver "0.81.0" - metro-runtime "0.81.0" - metro-source-map "0.81.0" - metro-symbolicate "0.81.0" - metro-transform-plugins "0.81.0" - metro-transform-worker "0.81.0" - mime-types "^2.1.27" - nullthrows "^1.1.1" - serialize-error "^2.1.0" - source-map "^0.5.6" - strip-ansi "^6.0.0" - throat "^5.0.0" - ws "^7.5.10" - yargs "^17.6.2" - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -micromatch@^4.0.7, micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -"mime-db@>= 1.43.0 < 2": - version "1.53.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" - integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== - -mime-types@^2.1.27, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.4.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^8.0.2: - version "8.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" - integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minipass@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": - version "7.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481" - integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA== - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@3.3.8: - version "3.3.8" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" - integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -negotiator@~0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" - integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== - -neo-async@^2.5.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nocache@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.4.tgz#5b37a56ec6e09fc7d401dceaed2eab40c8bfdf79" - integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== - -node-abort-controller@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -node-stream-zip@^1.9.1: - version "1.15.0" - resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" - integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nullthrows@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" - integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== - -ob1@0.81.0: - version "0.81.0" - resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.81.0.tgz#dc3154cca7aa9c2eb58f5ac63e9ee23ff4c6f520" - integrity sha512-6Cvrkxt1tqaRdWqTAMcVYEiO5i1xcF9y7t06nFdjFqkfPsEloCf8WwhXdwBpNUkVYSQlSGS7cDgVQR86miBfBQ== - dependencies: - flow-enums-runtime "^0.0.6" - -object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.4, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.entries@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -object.fromentries@^2.0.7: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.hasown@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" - integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== - dependencies: - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.values@^1.1.6, object.values@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^6.2.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" - integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== - dependencies: - is-wsl "^1.1.0" - -open@^7.0.3: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.6.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picocolors@^1.0.1, picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pirates@^4.0.4, pirates@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -postcss-value-parser@^4.0.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@8.4.38: - version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.2.0" - -pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== - dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^17.0.1" - -pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" - integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== - dependencies: - asap "~2.0.6" - -prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -query-string@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" - integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== - dependencies: - decode-uri-component "^0.2.2" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -queue@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" - integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== - dependencies: - inherits "~2.0.3" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -react-devtools-core@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-6.1.0.tgz#d6398a57dad7a1bc65ed84dceb423b3212200335" - integrity sha512-sA8gF/pUhjoGAN3s1Ya43h+F4Q0z7cv9RgqbUfhP7bJI0MbqeshLYFb6hiHgZorovGr8AXqhLi22eQ7V3pru/Q== - dependencies: - shell-quote "^1.6.1" - ws "^7" - -react-freeze@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.4.tgz#cbbea2762b0368b05cbe407ddc9d518c57c6f3ad" - integrity sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA== - -react-is@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -react-is@^18.0.0, react-is@^18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -react-native-base64@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/react-native-base64/-/react-native-base64-0.2.1.tgz#3d0e73a649c4c0129f7b7695d3912456aebae847" - integrity sha512-eHgt/MA8y5ZF0aHfZ1aTPcIkDWxza9AaEk4GcpIX+ZYfZ04RcaNahO+527KR7J44/mD3efYfM23O2C1N44ByWA== - -react-native-safe-area-context@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.1.0.tgz#0125f0c7762a2c189a3d067623ab8fbcdcb79cb8" - integrity sha512-Y4vyJX+0HPJUQNVeIJTj2/UOjbSJcB09OEwirAWDrOZ67Lz5p43AmjxSy8nnZft1rMzoh3rcPuonB6jJyHTfCw== - -react-native-screens@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.5.0.tgz#748d47ed68763ea28822a5ecf304dbef38a15652" - integrity sha512-yBWeN5EHNeew9f0ia9VE7JSlUQzCZEwkb87r7A7/Sg41OJHuRKHNRhmdCOiMBUqwwQi3F+b4NZGywjeM/gWMyg== - dependencies: - react-freeze "^1.0.0" - warn-once "^0.1.0" - -react-native-toast-message@^2.1.6: - version "2.2.0" - resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-2.2.0.tgz#c53a4746b15616858a7d61c4386b92cbe9fbf911" - integrity sha512-AFti8VzUk6JvyGAlLm9/BknTNDXrrhqnUk7ak/pM7uCTxDPveAu2ekszU0on6vnUPFnG04H/QfYE2IlETqeaWw== - -react-native@0.77.0: - version "0.77.0" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.77.0.tgz#ef194e6305cefde43d7ba5d242ceb9a1fddf9578" - integrity sha512-oCgHLGHFIp6F5UbyHSedyUXrZg6/GPe727freGFvlT7BjPJ3K6yvvdlsp7OEXSAHz6Fe7BI2n5cpUyqmP9Zn+Q== - dependencies: - "@jest/create-cache-key-function" "^29.6.3" - "@react-native/assets-registry" "0.77.0" - "@react-native/codegen" "0.77.0" - "@react-native/community-cli-plugin" "0.77.0" - "@react-native/gradle-plugin" "0.77.0" - "@react-native/js-polyfills" "0.77.0" - "@react-native/normalize-colors" "0.77.0" - "@react-native/virtualized-lists" "0.77.0" - abort-controller "^3.0.0" - anser "^1.4.9" - ansi-regex "^5.0.0" - babel-jest "^29.7.0" - babel-plugin-syntax-hermes-parser "0.25.1" - base64-js "^1.5.1" - chalk "^4.0.0" - commander "^12.0.0" - event-target-shim "^5.0.1" - flow-enums-runtime "^0.0.6" - glob "^7.1.1" - invariant "^2.2.4" - jest-environment-node "^29.6.3" - jsc-android "^250231.0.0" - memoize-one "^5.0.0" - metro-runtime "^0.81.0" - metro-source-map "^0.81.0" - nullthrows "^1.1.1" - pretty-format "^29.7.0" - promise "^8.3.0" - react-devtools-core "^6.0.1" - react-refresh "^0.14.0" - regenerator-runtime "^0.13.2" - scheduler "0.24.0-canary-efb381bbf-20230505" - semver "^7.1.3" - stacktrace-parser "^0.1.10" - whatwg-fetch "^3.0.0" - ws "^6.2.3" - yargs "^17.6.2" - -react-refresh@^0.14.0: - version "0.14.2" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" - integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== - -react-refresh@^0.4.0: - version "0.4.3" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.4.3.tgz#966f1750c191672e76e16c2efa569150cc73ab53" - integrity sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA== - -react@18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" - -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readline@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" - integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== - -recast@^0.23.9: - version "0.23.9" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.9.tgz#587c5d3a77c2cfcb0c18ccce6da4361528c2587b" - integrity sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q== - dependencies: - ast-types "^0.16.1" - esprima "~4.0.0" - source-map "~0.6.1" - tiny-invariant "^1.3.3" - tslib "^2.0.1" - -reflect.getprototypeof@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" - integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.1" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" - -regenerate-unicode-properties@^10.1.0: - version "10.1.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" - integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== - dependencies: - regenerate "^1.4.2" - -regenerate-unicode-properties@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" - integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.13.2: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regenerator-transform@^0.15.2: - version "0.15.2" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" - integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -regexpu-core@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" - integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== - dependencies: - "@babel/regjsgen" "^0.8.0" - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regexpu-core@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" - integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.2.0" - regjsgen "^0.8.0" - regjsparser "^0.12.0" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsgen@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" - integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== - -regjsparser@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" - integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== - dependencies: - jsesc "~3.0.2" - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -reselect@^4.1.7: - version "4.1.8" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" - integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.14.2, resolve@^1.22.8: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^2.0.0-next.5: - version "2.0.0-next.5" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" - integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - -scheduler@0.24.0-canary-efb381bbf-20230505: - version "0.24.0-canary-efb381bbf-20230505" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz#5dddc60e29f91cd7f8b983d7ce4a99c2202d178f" - integrity sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA== - dependencies: - loose-envify "^1.1.0" - -selfsigned@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - -semver@^5.6.0: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.1.3, semver@^7.6.0: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -semver@^7.3.7: - version "7.6.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== - -semver@^7.5.2: - version "7.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.0.tgz#9c6fe61d0c6f9fa9e26575162ee5a9180361b09c" - integrity sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ== - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-error@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" - integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== - -serve-static@^1.13.1, serve-static@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.1, set-function-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shallowequal@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.6.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -shell-quote@^1.7.3: - version "1.8.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" - integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== - -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - -source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - -source-map-support@^0.5.16, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -stacktrace-parser@^0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" - integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== - dependencies: - type-fest "^0.7.1" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-natural-compare@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" - integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.matchall@^4.0.10: - version "4.0.11" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" - integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - regexp.prototype.flags "^1.5.2" - set-function-name "^2.0.2" - side-channel "^1.0.6" - -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^5.0.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - -styled-components@^6.0.7: - version "6.1.11" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.11.tgz#01948e5195bf1d39e57e0a85b41958c80e40cfb8" - integrity sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA== - dependencies: - "@emotion/is-prop-valid" "1.2.2" - "@emotion/unitless" "0.8.1" - "@types/stylis" "4.2.5" - css-to-react-native "3.2.0" - csstype "3.1.3" - postcss "8.4.38" - shallowequal "1.1.0" - stylis "4.3.2" - tslib "2.6.2" - -stylis@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.2.tgz#8f76b70777dd53eb669c6f58c997bf0a9972e444" - integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg== - -sudo-prompt@^9.0.0: - version "9.2.1" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" - integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -terser@^5.15.0: - version "5.31.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1" - integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== - -through2@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -tiny-invariant@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" - integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== - -tmp@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" - integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -ts-api-utils@^1.3.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" - integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== - -tslib@2.6.2, tslib@^2.0.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" - integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== - -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.0.13: - version "1.0.15" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz#60ed9f8cba4a728b7ecf7356f641a31e3a691d97" - integrity sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA== - dependencies: - escalade "^3.1.2" - picocolors "^1.0.0" - -update-browserslist-db@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" - integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -use-latest-callback@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16" - integrity sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ== - -use-sync-external-store@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" - integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vlq@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" - integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== - -walker@^1.0.7, walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -warn-once@^0.1.0, warn-once@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" - integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -whatwg-fetch@^3.0.0: - version "3.6.20" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" - integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-builtin-type@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== - dependencies: - function.prototype.name "^1.1.5" - has-tostringtag "^1.0.0" - is-async-function "^2.0.0" - is-date-object "^1.0.5" - is-finalizationregistry "^1.0.2" - is-generator-function "^1.0.10" - is-regex "^1.1.4" - is-weakref "^1.0.2" - isarray "^2.0.5" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.9" - -which-collection@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" - integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== - dependencies: - is-map "^2.0.3" - is-set "^2.0.3" - is-weakmap "^2.0.2" - is-weakset "^2.0.3" - -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -write-file-atomic@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" - integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - -ws@^6.2.3: - version "6.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.3.tgz#ccc96e4add5fd6fedbc491903075c85c5a11d9ee" - integrity sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA== - dependencies: - async-limiter "~1.0.0" - -ws@^7: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - -ws@^7.5.10: - version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" - integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== - -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yaml@^2.2.1: - version "2.7.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98" - integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA== - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^15.1.0: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@^17.6.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/integration-tests/hardware/maestro/01-scan-connect-discover.yaml b/integration-tests/hardware/maestro/01-scan-connect-discover.yaml new file mode 100644 index 000000000..582dd8e1b --- /dev/null +++ b/integration-tests/hardware/maestro/01-scan-connect-discover.yaml @@ -0,0 +1,31 @@ +# Test 01: Scan -> Connect -> Discover Services +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +# Verify all 5 test characteristics are visible +- assertVisible: + id: "char-read-counter" +- assertVisible: + id: "char-write-echo" +- assertVisible: + id: "char-notify-stream" +- assertVisible: + id: "char-indicate-stream" +- assertVisible: + id: "char-mtu-test" + +# Verify MTU is displayed +- extendedWaitUntil: + visible: + id: "device-mtu" + timeout: 3000 + +# Clean disconnect +- tapOn: + id: "disconnect-btn" + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/02-read-counter.yaml b/integration-tests/hardware/maestro/02-read-counter.yaml new file mode 100644 index 000000000..a2a2cbc35 --- /dev/null +++ b/integration-tests/hardware/maestro/02-read-counter.yaml @@ -0,0 +1,36 @@ +# Test 02: Read Counter Characteristic +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-read-counter" + +- extendedWaitUntil: + visible: + id: "read-btn" + timeout: 5000 + +# First read +- tapOn: + id: "read-btn" + +- extendedWaitUntil: + visible: + id: "value-display" + timeout: 5000 + +# Verify value is no longer the default +- assertNotVisible: + text: "Value: (none)" + +# Second read (counter should increment) +- tapOn: + id: "read-btn" + +- waitForAnimationToEnd + +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Value: (none)" diff --git a/integration-tests/hardware/maestro/03-write-read-echo.yaml b/integration-tests/hardware/maestro/03-write-read-echo.yaml new file mode 100644 index 000000000..e3b72f915 --- /dev/null +++ b/integration-tests/hardware/maestro/03-write-read-echo.yaml @@ -0,0 +1,34 @@ +# Test 03: Write -> Read Echo Roundtrip +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-write-echo" + +- extendedWaitUntil: + visible: + id: "write-input" + timeout: 5000 + +# Write "Hello" (base64: SGVsbG8=) +- tapOn: + id: "write-input" +- eraseText: 50 +- inputText: "SGVsbG8=" + +- tapOn: + id: "write-btn" + +# Wait for write to complete +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Read back and verify the echoed value +- tapOn: + id: "read-btn" + +- extendedWaitUntil: + visible: + text: "SGVsbG8=" + timeout: 5000 diff --git a/integration-tests/hardware/maestro/04-notify-stream.yaml b/integration-tests/hardware/maestro/04-notify-stream.yaml new file mode 100644 index 000000000..c377e4e40 --- /dev/null +++ b/integration-tests/hardware/maestro/04-notify-stream.yaml @@ -0,0 +1,56 @@ +# Test 04: Notification Stream +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-notify-stream" + +- extendedWaitUntil: + visible: + id: "monitor-toggle" + timeout: 5000 + +# Verify counters start at 0 +- assertVisible: + id: "sample-count" + +# Enable monitoring +- tapOn: + id: "monitor-toggle" + +# Wait 10 seconds for notifications to stream in (500ms interval = ~20 expected) +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Assert we received samples (not still at 0-4) +- assertNotVisible: + text: "Samples: 0" +- assertNotVisible: + text: "Samples: 1" +- assertNotVisible: + text: "Samples: 2" +- assertNotVisible: + text: "Samples: 3" +- assertNotVisible: + text: "Samples: 4" + +# Assert at least 2 distinct values (proves counter is incrementing) +- assertNotVisible: + text: "Distinct: 0" +- assertNotVisible: + text: "Distinct: 1" + +# Disable monitoring +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd diff --git a/integration-tests/hardware/maestro/05-indicate-stream.yaml b/integration-tests/hardware/maestro/05-indicate-stream.yaml new file mode 100644 index 000000000..4c2bf3d0f --- /dev/null +++ b/integration-tests/hardware/maestro/05-indicate-stream.yaml @@ -0,0 +1,54 @@ +# Test 05: Indication Stream +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-indicate-stream" + +- extendedWaitUntil: + visible: + id: "monitor-toggle" + timeout: 5000 + +# Verify counters start at 0 +- assertVisible: + id: "sample-count" + +# Enable monitoring +- tapOn: + id: "monitor-toggle" + +# Wait 12 seconds for indications (1000ms interval = ~12 expected) +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Assert we received at least 3 samples +- assertNotVisible: + text: "Samples: 0" +- assertNotVisible: + text: "Samples: 1" +- assertNotVisible: + text: "Samples: 2" + +# Assert at least 2 distinct values +- assertNotVisible: + text: "Distinct: 0" +- assertNotVisible: + text: "Distinct: 1" + +# Disable monitoring +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd diff --git a/integration-tests/hardware/maestro/06-mtu-read.yaml b/integration-tests/hardware/maestro/06-mtu-read.yaml new file mode 100644 index 000000000..816d02bca --- /dev/null +++ b/integration-tests/hardware/maestro/06-mtu-read.yaml @@ -0,0 +1,30 @@ +# Test 06: MTU Read +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +# Verify MTU is displayed on DeviceScreen +- extendedWaitUntil: + visible: + id: "device-mtu" + timeout: 3000 + +- tapOn: + id: "char-mtu-test" + +- extendedWaitUntil: + visible: + id: "read-btn" + timeout: 5000 + +- tapOn: + id: "read-btn" + +- extendedWaitUntil: + visible: + id: "value-display" + timeout: 5000 + +# Verify we got an actual value +- assertNotVisible: + text: "Value: (none)" diff --git a/integration-tests/hardware/maestro/07-disconnect-reconnect.yaml b/integration-tests/hardware/maestro/07-disconnect-reconnect.yaml new file mode 100644 index 000000000..c56b43998 --- /dev/null +++ b/integration-tests/hardware/maestro/07-disconnect-reconnect.yaml @@ -0,0 +1,61 @@ +# Test 07: Disconnect and Reconnect +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +# Disconnect +- tapOn: + id: "disconnect-btn" + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 + +# Re-scan +- tapOn: + id: "scan-start-btn" + +# Filter by name to find BlePlxTest +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 20000 + +# Tap BlePlxTest device row to reconnect +- tapOn: + id: "device-BlePlxTest" + +# Verify reconnected +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 15000 + +# Re-discover +- tapOn: + id: "discover-btn" + +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 + +- extendedWaitUntil: + visible: + id: "char-read-counter" + timeout: 5000 + +# Final disconnect +- tapOn: + id: "disconnect-btn" + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/08-write-no-response.yaml b/integration-tests/hardware/maestro/08-write-no-response.yaml new file mode 100644 index 000000000..0dca92f6b --- /dev/null +++ b/integration-tests/hardware/maestro/08-write-no-response.yaml @@ -0,0 +1,35 @@ +# Test 08: Write Without Response +# Writes a value using write-without-response, then reads back to verify echo +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-write-no-response" + +- extendedWaitUntil: + visible: + id: "write-input" + timeout: 5000 + +# Write "Test" (base64: VGVzdA==) without response +- tapOn: + id: "write-input" +- eraseText: 50 +- inputText: "VGVzdA==" + +- tapOn: + id: "write-btn" + +# Wait for write to complete +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Read back the echoed value +- tapOn: + id: "read-btn" + +- extendedWaitUntil: + visible: + text: "VGVzdA==" + timeout: 5000 diff --git a/integration-tests/hardware/maestro/09-scan-uuid-filter.yaml b/integration-tests/hardware/maestro/09-scan-uuid-filter.yaml new file mode 100644 index 000000000..074e784f7 --- /dev/null +++ b/integration-tests/hardware/maestro/09-scan-uuid-filter.yaml @@ -0,0 +1,65 @@ +# Test 09: Scan with Service UUID Filter +# Scans with the test service UUID filter and verifies only BlePlxTest appears +# (or at least far fewer devices than unfiltered scan) +appId: com.bleplxexample +--- +# Stop peripheral apps and restart scanner fresh +- stopApp: + appId: "com.bleplx.testperipheral" +- stopApp: + appId: "com.bleplx.testperipheral.ios" +- stopApp +- launchApp + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 5000 + +- extendedWaitUntil: + visible: + id: "ble-state" + timeout: 10000 + +# Enable UUID filter before scanning +- tapOn: + id: "scan-filter-btn" + +# Verify filter is active (text match on filter button label) +- assertVisible: + id: "scan-filter-btn" + +# Start filtered scan +- tapOn: + id: "scan-start-btn" + +# Type in search to filter for BlePlxTest +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +# Wait for BlePlxTest to appear — it should be found since it advertises the test service UUID +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 20000 + +# Stop scan +- tapOn: + id: "scan-stop-btn" + +- waitForAnimationToEnd + +# At minimum, BlePlxTest should be present +- assertVisible: + id: "device-BlePlxTest" + +# Verify we can still connect through filtered scan +- tapOn: + id: "device-BlePlxTest" + +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 15000 diff --git a/integration-tests/hardware/maestro/10-background-mode-ios.yaml b/integration-tests/hardware/maestro/10-background-mode-ios.yaml new file mode 100644 index 000000000..184e61595 --- /dev/null +++ b/integration-tests/hardware/maestro/10-background-mode-ios.yaml @@ -0,0 +1,57 @@ +# Test 10 (iOS variant): Background Mode +# Same as 10-background-mode.yaml but uses iOS Settings app to background +appId: com.iotashan.example.--PRODUCT-NAME-rfc1034identifier- +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-notify-stream" + +- extendedWaitUntil: + visible: + id: "monitor-toggle" + timeout: 5000 + +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd +- waitForAnimationToEnd + +- assertNotVisible: + text: "Samples: 0" + +# Background by launching iOS Settings +- launchApp: + appId: "com.apple.Preferences" + +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Return to our app +- launchApp: + appId: "com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-" + +- waitForAnimationToEnd + +- extendedWaitUntil: + visible: + id: "value-display" + timeout: 5000 + +- assertNotVisible: + text: "Samples: 0" +- assertNotVisible: + text: "Samples: 1" +- assertNotVisible: + text: "Samples: 2" +- assertNotVisible: + text: "Samples: 3" +- assertNotVisible: + text: "Samples: 4" + +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd diff --git a/integration-tests/hardware/maestro/10-background-mode.yaml b/integration-tests/hardware/maestro/10-background-mode.yaml new file mode 100644 index 000000000..e02fc45dd --- /dev/null +++ b/integration-tests/hardware/maestro/10-background-mode.yaml @@ -0,0 +1,69 @@ +# Test 10: Background Mode +# Connects, starts monitoring, backgrounds the app, then returns to verify +# monitoring is still active (sample count increased while backgrounded) +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +# Navigate to Notify Stream +- tapOn: + id: "char-notify-stream" + +- extendedWaitUntil: + visible: + id: "monitor-toggle" + timeout: 5000 + +# Enable monitoring +- tapOn: + id: "monitor-toggle" + +# Wait for some samples to arrive +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Verify monitoring is working +- assertNotVisible: + text: "Samples: 0" + +# Background the app by launching Settings +- launchApp: + appId: "com.android.settings" + +# Wait 3 seconds in the background +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Return to our app +- launchApp: + appId: "com.bleplxexample" + +# Wait for the app to come back +- waitForAnimationToEnd + +# Verify we're still on the CharacteristicScreen and monitoring continued +- extendedWaitUntil: + visible: + id: "value-display" + timeout: 5000 + +# The sample count should have increased while we were away +# If it was at ~4 before backgrounding and 3s passed (500ms interval = ~6 more), +# we should have >= 5 now +- assertNotVisible: + text: "Samples: 0" +- assertNotVisible: + text: "Samples: 1" +- assertNotVisible: + text: "Samples: 2" +- assertNotVisible: + text: "Samples: 3" +- assertNotVisible: + text: "Samples: 4" + +# Disable monitoring +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd diff --git a/integration-tests/hardware/maestro/11-l2cap-channel.yaml b/integration-tests/hardware/maestro/11-l2cap-channel.yaml new file mode 100644 index 000000000..3deb9b27f --- /dev/null +++ b/integration-tests/hardware/maestro/11-l2cap-channel.yaml @@ -0,0 +1,56 @@ +# Test 11: L2CAP Channel +# Reads the L2CAP PSM from the peripheral, opens a channel, writes data, closes it +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +# Tap the L2CAP PSM characteristic — this reads the PSM and navigates to L2CAP screen +- tapOn: + id: "char-l2cap-psm" + +# Wait for L2CAP screen to load +- extendedWaitUntil: + visible: + id: "l2cap-open-btn" + timeout: 5000 + +# Verify PSM is displayed +- assertVisible: + id: "l2cap-psm" + +# Open the L2CAP channel +- tapOn: + id: "l2cap-open-btn" + +# Wait for connection +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +# Verify channel ID is shown (proves connection succeeded) +- assertVisible: + id: "l2cap-channel-id" + +# Write some data (base64 "Hello" = SGVsbG8=) +- tapOn: + id: "l2cap-write-input" +- eraseText: 50 +- inputText: "SGVsbG8=" + +- tapOn: + id: "l2cap-write-btn" + +# Wait for write to complete +- waitForAnimationToEnd +- waitForAnimationToEnd + +# No error should have appeared +- assertNotVisible: + id: "l2cap-error" + +# Close the channel +- tapOn: + id: "l2cap-close-btn" + +- waitForAnimationToEnd +- waitForAnimationToEnd diff --git a/integration-tests/hardware/maestro/12-request-mtu.yaml b/integration-tests/hardware/maestro/12-request-mtu.yaml new file mode 100644 index 000000000..594856fe1 --- /dev/null +++ b/integration-tests/hardware/maestro/12-request-mtu.yaml @@ -0,0 +1,19 @@ +# Test 12: Request MTU (Android only) +# Requests MTU 247 and verifies the request succeeds +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- assertVisible: + id: "device-mtu" + +- tapOn: + id: "request-mtu-btn" + +- extendedWaitUntil: + visible: + id: "mtu-request-status" + timeout: 5000 + +- assertVisible: + text: "Success:" diff --git a/integration-tests/hardware/maestro/13-monitor-unsubscribe.yaml b/integration-tests/hardware/maestro/13-monitor-unsubscribe.yaml new file mode 100644 index 000000000..aa8b96b9c --- /dev/null +++ b/integration-tests/hardware/maestro/13-monitor-unsubscribe.yaml @@ -0,0 +1,36 @@ +# Test 13: Monitor Unsubscribe Verification +# Verifies that disabling monitor stops notifications +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-notify-stream" + +- extendedWaitUntil: + visible: + id: "monitor-toggle" + timeout: 5000 + +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +- assertNotVisible: + text: "Samples: 0" + +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd +- waitForAnimationToEnd + +- assertVisible: + text: "Monitor: STOPPED" diff --git a/integration-tests/hardware/maestro/14-invalid-write.yaml b/integration-tests/hardware/maestro/14-invalid-write.yaml new file mode 100644 index 000000000..884df1cfe --- /dev/null +++ b/integration-tests/hardware/maestro/14-invalid-write.yaml @@ -0,0 +1,29 @@ +# Test 14: Invalid Base64 Write +# Verifies that writing invalid base64 shows an error +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-write-echo" + +- extendedWaitUntil: + visible: + id: "write-input" + timeout: 5000 + +- tapOn: + id: "write-input" +- eraseText: 50 +- inputText: "!!!NOT-BASE64!!!" + +- tapOn: + id: "write-btn" + +- waitForAnimationToEnd +- waitForAnimationToEnd + +- extendedWaitUntil: + visible: + id: "write-error" + timeout: 5000 diff --git a/integration-tests/hardware/maestro/15-l2cap-echo-verify.yaml b/integration-tests/hardware/maestro/15-l2cap-echo-verify.yaml new file mode 100644 index 000000000..ca2c56b05 --- /dev/null +++ b/integration-tests/hardware/maestro/15-l2cap-echo-verify.yaml @@ -0,0 +1,50 @@ +# Test 15: L2CAP Echo Data Verification +# Opens L2CAP channel, writes data, verifies echo received +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-l2cap-psm" + +- extendedWaitUntil: + visible: + id: "l2cap-open-btn" + timeout: 5000 + +- tapOn: + id: "l2cap-open-btn" + +# Wait for channel to actually open (BLE async, not UI animation) +- extendedWaitUntil: + visible: + id: "l2cap-channel-id" + timeout: 8000 + +- tapOn: + id: "l2cap-write-input" +- eraseText: 50 +- inputText: "SGVsbG8=" + +- tapOn: + id: "l2cap-write-btn" + +# Wait for echo data +- extendedWaitUntil: + visible: + id: "l2cap-rx-data" + timeout: 5000 + +# Verify echo content matches what was sent +- assertVisible: + text: "SGVsbG8=" + +# Close the channel +- tapOn: + id: "l2cap-close-btn" + +- waitForAnimationToEnd + +# Verify channel closed +- assertNotVisible: + id: "l2cap-channel-id" diff --git a/integration-tests/hardware/maestro/16-payload-boundaries.yaml b/integration-tests/hardware/maestro/16-payload-boundaries.yaml new file mode 100644 index 000000000..58d8a12bc --- /dev/null +++ b/integration-tests/hardware/maestro/16-payload-boundaries.yaml @@ -0,0 +1,51 @@ +# Test 16: Payload Boundary Tests +# Writes 1-byte and 20-byte payloads, reads back, verifies +appId: com.bleplxexample +--- +- runFlow: _connect-and-discover.yaml + +- tapOn: + id: "char-write-echo" + +- extendedWaitUntil: + visible: + id: "write-input" + timeout: 5000 + +- tapOn: + id: "write-1byte-btn" + +- tapOn: + id: "write-btn" +- waitForAnimationToEnd +- waitForAnimationToEnd + +- tapOn: + id: "read-btn" + +- extendedWaitUntil: + visible: + text: "QQ==" + timeout: 5000 + +- assertVisible: + text: "Payload: 1 bytes" + +- tapOn: + id: "write-20byte-btn" + +- tapOn: + id: "write-btn" +- waitForAnimationToEnd +- waitForAnimationToEnd + +- tapOn: + id: "read-btn" + +- extendedWaitUntil: + visible: + text: "QUJDREVGR0hJSktMTU5PUFFSU1Q=" + timeout: 5000 + +- assertVisible: + text: "Payload: 20 bytes" diff --git a/integration-tests/hardware/maestro/17-permissions-denied.yaml b/integration-tests/hardware/maestro/17-permissions-denied.yaml new file mode 100644 index 000000000..f5c62c2ed --- /dev/null +++ b/integration-tests/hardware/maestro/17-permissions-denied.yaml @@ -0,0 +1,36 @@ +# Test 17: Permission Denied (Android only) +# Verifies scan error is displayed when BLE permissions are revoked +# NOTE: Permissions are revoked by run-e2e.sh before this test +appId: com.bleplxexample +--- +- stopApp +- launchApp + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 15000 + +# Dismiss permission dialogs if they appear +- tapOn: + text: "Don't allow" + optional: true +- tapOn: + text: "Don't allow" + optional: true +- tapOn: + text: "Don't allow" + optional: true + +- waitForAnimationToEnd + +- tapOn: + id: "scan-start-btn" + +- waitForAnimationToEnd +- waitForAnimationToEnd + +- extendedWaitUntil: + visible: + id: "scan-error" + timeout: 5000 diff --git a/integration-tests/hardware/maestro/E2E-PLAN.md b/integration-tests/hardware/maestro/E2E-PLAN.md new file mode 100644 index 000000000..935ebe862 --- /dev/null +++ b/integration-tests/hardware/maestro/E2E-PLAN.md @@ -0,0 +1,81 @@ +# E2E BLE Test Plan + +## Architecture + +Two physical devices: iPhone (UDID: 00008130-000A34C12021401C) and Android (ID: 1A211FDF60055L). +Each test configuration uses one device as peripheral (BlePlxTest) and the other as scanner (example app). + +### Configurations +- `android-scans-ios`: Android=scanner, iPhone=BlePlxTest peripheral +- `ios-scans-android`: iPhone=scanner, Android=BlePlxTest peripheral + +### Orchestration +Shell script `run-e2e.sh `: +1. Kill apps on both devices +2. Launch BlePlxTest on peripheral device (adb/devicectl) +3. Wait with bounded readiness check (up to 15s) +4. Run Maestro flows against scanner device +5. Collect results + +### Shared Subflow +`_connect-and-discover.yaml` — every test imports this: +1. Launch app fresh +2. Wait for PoweredOn +3. Start scan +4. Wait for "BlePlxTest" (up to 20s) +5. **Stop scan** (critical: some Android devices fail connect during active scan) +6. Tap BlePlxTest to connect +7. Wait for DeviceScreen +8. Discover services +9. Expand test service + +## Test Matrix + +| Flow | Feature | Characteristics | Assertions | +|------|---------|----------------|------------| +| 01-scan-connect-discover | Scan, connect, discover | - | Services listed, test service expandable | +| 02-read-counter | Read characteristic | 9A01 | Value changes between reads (read twice, compare) | +| 03-write-read-echo | Write + read roundtrip | 9A02 | Written base64 matches readback | +| 04-notify-stream | Notification monitoring | 9A03 | sample_count >= 5 over 5s, distinct_count >= 2 | +| 05-indicate-stream | Indication monitoring | 9A04 | sample_count >= 3 over 5s, distinct_count >= 2 | +| 06-mtu-read | MTU query + characteristic | 9A05 | getMtu() > 0, MTU char readable | +| 07-disconnect-reconnect | Disconnect and reconnect | - | Clean disconnect, re-scan finds device, reconnect succeeds | + +## Semantic TestIDs (added to example app) + +### ScanScreen +- `scan-start-btn`, `scan-stop-btn`, `device-list`, `device-item-{id}` +- `ble-state` — displays current BLE state text + +### DeviceScreen +- `discover-btn`, `disconnect-btn` +- `device-mtu` — displays MTU value +- `service-test` — the test service header (semantic, not UUID-based) +- `char-read-counter`, `char-write-echo`, `char-notify-stream`, `char-indicate-stream`, `char-mtu-test` + +### CharacteristicScreen +- `read-btn`, `write-input`, `write-btn`, `monitor-toggle` +- `value-display` — current value +- `sample-count` — number of monitor samples received +- `distinct-count` — number of distinct values received + +## Features NOT Testable via Maestro E2E + +These features CANNOT be tested with the current setup and must be noted: + +1. **L2CAP Channels** (openL2CAPChannel, writeL2CAPChannel, closeL2CAPChannel) — Neither test peripheral app implements L2CAP PSM. Would require significant peripheral work. +2. **PHY Requests** (requestPhy, readPhy) — Android-only API; iOS doesn't expose PHY settings. No observable UI effect. +3. **Connection Priority** (requestConnectionPriority) — Android-only; no observable effect in UI, affects radio timing only. +4. **Bond State** (onBondStateChange, getBondedDevices) — Requires system pairing dialog which Maestro cannot control. +5. **State Restoration** (onRestoreState) — Requires killing the app process while connected and relaunching with CoreBluetooth background restoration. Complex to orchestrate. +6. **Write Without Response** — Test peripheral's Write Echo uses PROPERTY_WRITE (with response). Would need a separate characteristic. +7. **Permission Denied Flows** — Maestro cannot reliably intercept/deny system permission dialogs. +8. **Multiple Simultaneous Connections** — Only one BlePlxTest peripheral per device. +9. **Transaction Cancellation** (cancelTransaction) — Requires mid-flight cancel timing that's impractical in UI tests. +10. **Authorization Status** (getAuthorizationStatus) — Returns current state but can't be toggled via Maestro. +11. **Scan with Service UUID Filter** — Could test but adds complexity; scanning without filter covers the code path. +12. **Background Mode / App Lifecycle** — Would need Maestro to background/foreground the app during active connections. + +## iOS Peripheral Fixes Needed +1. Backpressure handling: `updateValue` can return false when queue is full; must wait for `peripheralManagerIsReady(toUpdateSubscribers:)` +2. Keep app foregrounded during tests (CoreBluetooth reduces advertising when backgrounded) diff --git a/integration-tests/hardware/maestro/README.md b/integration-tests/hardware/maestro/README.md new file mode 100644 index 000000000..903f3b253 --- /dev/null +++ b/integration-tests/hardware/maestro/README.md @@ -0,0 +1,133 @@ +# BLE E2E Tests (Maestro) + +Automated end-to-end tests for react-native-ble-plx using two physical devices. + +## Setup + +### Devices +- **iPhone**: UDID `00008130-000A34C12021401C` +- **Android**: ID `1A211FDF60055L` + +### Apps Required +| App | Android Package | iOS Bundle ID | +|-----|----------------|---------------| +| Example (scanner) | `com.bleplxexample` | `com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-` | +| BlePlxTest (peripheral) | `com.bleplx.testperipheral` | `com.bleplx.testperipheral.ios` | + +### Prerequisites +1. Both devices connected (`adb devices`, `xcrun devicectl list devices`) +2. All 4 apps installed on appropriate devices +3. BLE permissions pre-granted on both devices +4. Metro bundler running for the example app +5. `maestro` CLI installed (`brew install maestro`) + +### BLE Permissions + +#### Android (API 31+) +```bash +adb shell pm grant com.bleplxexample android.permission.BLUETOOTH_SCAN +adb shell pm grant com.bleplxexample android.permission.BLUETOOTH_CONNECT +adb shell pm grant com.bleplxexample android.permission.ACCESS_FINE_LOCATION +``` + +#### iOS +Grant BLE and location permissions manually on first launch, then terminate and re-launch. + +## Running Tests + +```bash +# Android as scanner, iPhone as peripheral +./run-e2e.sh android + +# iPhone as scanner, Android as peripheral +./run-e2e.sh ios + +# Run a specific test only +./run-e2e.sh android 03 # write-read-echo test +./run-e2e.sh ios 04 # notify-stream test +``` + +## Test Flows + +| File | Test | What it validates | +|------|------|------------------| +| `_connect-and-discover.yaml` | (shared subflow) | Scan, stop scan, connect, discover, expand test service | +| `01-scan-connect-discover.yaml` | Scan/Connect/Discover | Full happy path, all 5 characteristics visible, MTU shown | +| `02-read-counter.yaml` | Read Characteristic | Read counter returns a value, second read returns different value | +| `03-write-read-echo.yaml` | Write/Read Roundtrip | Write base64 "SGVsbG8=", read back, verify match | +| `04-notify-stream.yaml` | Notification Stream | 5s of notifications, >= 5 samples, >= 2 distinct values | +| `05-indicate-stream.yaml` | Indication Stream | 6s of indications, >= 3 samples, >= 2 distinct values | +| `06-mtu-read.yaml` | MTU Query | MTU on DeviceScreen, MTU characteristic readable | +| `07-disconnect-reconnect.yaml` | Disconnect/Reconnect | Disconnect, re-scan, reconnect, re-discover | + +### Legacy Flows (pre-v4, may need updating) +| File | Notes | +|------|-------| +| `scan-pair-sync.yaml` | Uses old UUID scheme (fff1-fff3) | +| `write-read-roundtrip.yaml` | Uses old UUID scheme | +| `disconnect-recovery.yaml` | Requires manual firmware disconnect trigger | +| `indicate-stress.yaml` | Uses old UUID scheme | + +## TestID Reference + +### ScanScreen +| testID | Element | +|--------|---------| +| `scan-start-btn` | Start Scan button | +| `scan-stop-btn` | Stop Scan button | +| `device-list` | FlatList of devices | +| `device-item-{id}` | Individual device row | + +### DeviceScreen +| testID | Element | +|--------|---------| +| `discover-btn` | Discover Services button | +| `disconnect-btn` | Disconnect button | +| `device-mtu` | MTU display text | +| `service-test` | Test service header (expandable) | +| `service-list` | FlatList of services | +| `test-char-list` | Expanded test characteristic list | +| `char-read-counter` | Read Counter characteristic | +| `char-write-echo` | Write Echo characteristic | +| `char-notify-stream` | Notify Stream characteristic | +| `char-indicate-stream` | Indicate Stream characteristic | +| `char-mtu-test` | MTU Test characteristic | + +### CharacteristicScreen +| testID | Element | +|--------|---------| +| `read-btn` | Read button | +| `write-input` | Write value TextInput | +| `write-btn` | Write button | +| `monitor-toggle` | Monitor enable/disable Switch | +| `value-display` | Current value display | +| `sample-count` | Monitor sample count (e.g. "Samples: 12") | +| `distinct-count` | Monitor distinct value count (e.g. "Distinct: 8") | + +## Features NOT Covered by E2E Tests + +These react-native-ble-plx features **cannot** be tested with the current Maestro E2E setup: + +| Feature | API Methods | Reason | +|---------|------------|--------| +| **L2CAP Channels** | `openL2CAPChannel`, `writeL2CAPChannel`, `closeL2CAPChannel` | Neither test peripheral implements L2CAP PSM | +| **PHY Requests** | `requestPhy`, `readPhy` | Android-only API; no observable UI effect | +| **Connection Priority** | `requestConnectionPriority` | Android-only; affects radio timing only, no UI feedback | +| **Bond State / Pairing** | `onBondStateChange`, `getBondedDevices` | Requires system pairing dialog that Maestro can't control | +| **State Restoration** | `onRestoreState` | Requires killing app during active BLE connection and relaunching with CoreBluetooth background restoration | +| **Write Without Response** | `writeCharacteristicForDevice(_, _, _, _, false)` | Test peripheral's Write Echo uses write-with-response only; would need a 6th characteristic | +| **Permission Denied Flows** | N/A | Maestro cannot reliably deny system permission dialogs | +| **Multiple Connections** | N/A | Only one BlePlxTest peripheral available per device | +| **Transaction Cancellation** | `cancelTransaction` | Requires canceling mid-flight; timing is impractical in UI tests | +| **Authorization Status** | `getAuthorizationStatus` | Read-only; cannot toggle Bluetooth authorization via Maestro | +| **Background Mode** | N/A | Would require backgrounding the app during active connections | +| **Scan with UUID Filter** | `startDeviceScan([uuids], ...)` | Covered by unfiltered scan code path; filter is a CoreBluetooth/Android pass-through | + +## Architecture Notes + +- **Stop scan before connect**: The shared subflow explicitly stops scanning before connecting. Some Android devices fail connection attempts during active scans. +- **Auto-expand test service**: After discovery, DeviceScreen auto-expands the test service so characteristics are immediately visible to Maestro. +- **Sample/distinct counters**: CharacteristicScreen tracks notification/indication sample counts for reliable stream assertions. +- **Semantic testIDs**: Characteristic rows use semantic IDs (`char-read-counter`) not raw UUIDs, avoiding casing issues across platforms. +- **iOS peripheral backpressure**: The iOS BlePlxTest handles `updateValue` returning `false` and waits for `peripheralManagerIsReady(toUpdateSubscribers:)`. +- **Fire-and-forget peripheral**: The peripheral app is launched on the server device and left running. Maestro only controls the scanner device. diff --git a/integration-tests/hardware/maestro/_connect-and-discover.yaml b/integration-tests/hardware/maestro/_connect-and-discover.yaml new file mode 100644 index 000000000..3148d21ee --- /dev/null +++ b/integration-tests/hardware/maestro/_connect-and-discover.yaml @@ -0,0 +1,62 @@ +# Shared subflow: Connect to BlePlxTest and discover services +appId: com.bleplxexample +--- + +# 1. Kill BlePlxTest if it's running on this device (avoid conflict) +- stopApp: + appId: "com.bleplx.testperipheral" +- stopApp: + appId: "com.bleplx.testperipheral.ios" + +# 2. Stop and relaunch the scanner app fresh +- stopApp +- launchApp + +# 3. Wait for app to load and BLE to power on +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 15000 + +# 4. Start scanning +- tapOn: + id: "scan-start-btn" + +# 5. Filter the list by typing in search box +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +# 6. Wait for BlePlxTest device row to appear +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 20000 + +# 7. Tap BlePlxTest device row to connect +- tapOn: + id: "device-BlePlxTest" + +# 8. Wait for DeviceScreen to load (connection established) +# BLE connection can take several seconds to complete +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 30000 + +# 9. Discover services +- tapOn: + id: "discover-btn" + +# 10. Wait for the test service to appear and auto-expand +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 + +# 11. Verify the test characteristics are visible +- extendedWaitUntil: + visible: + id: "char-read-counter" + timeout: 5000 diff --git a/integration-tests/hardware/maestro/disconnect-recovery.yaml b/integration-tests/hardware/maestro/disconnect-recovery.yaml new file mode 100644 index 000000000..bf7118281 --- /dev/null +++ b/integration-tests/hardware/maestro/disconnect-recovery.yaml @@ -0,0 +1,127 @@ +# Maestro flow: Disconnect Recovery +# +# Verifies that the library correctly handles an unexpected disconnection +# (e.g., the peripheral sends a disconnect command via serial/firmware trigger) +# and that the app can re-scan and reconnect successfully. +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on and advertising within range. +# - The test app is installed and foregrounded on the device under test. +# - BLE permissions have been pre-granted (see README.md). +# - A serial/GPIO mechanism is available to send the "disconnect" command to +# the firmware mid-test (see notes in step 3 below). +# +# Run: +# maestro test integration-tests/hardware/maestro/disconnect-recovery.yaml + +appId: com.bleplxexample + +--- + +# --------------------------------------------------------------------------- +# 1. Launch and scan +# --------------------------------------------------------------------------- +- launchApp: + clearState: true + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 5000 + +- extendedWaitUntil: + visible: + id: "ble-state" + timeout: 10000 + +- tapOn: + id: "scan-start-btn" + +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 15000 + +# --------------------------------------------------------------------------- +# 2. Connect to BlePlxTest +# --------------------------------------------------------------------------- +- tapOn: + id: "device-BlePlxTest" + +# Confirm we have reached DeviceScreen (connected state) +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 10000 + +- assertVisible: + id: "disconnect-btn" + +# --------------------------------------------------------------------------- +# 3. Trigger an unexpected disconnection from the firmware side +# +# MANUAL / AUTOMATION STEP: +# Send the "disconnect" command to the BlePlxTest firmware over serial so the +# peripheral drops the connection without the app initiating it. +# +# When using a serial relay or automated test harness, insert the relevant +# command here. For example, with a Python serial script: +# - evalScript: ${require('child_process').execSync('python3 scripts/serial_cmd.py disconnect')} +# +# When running manually: power off or reset the peripheral now. +# --------------------------------------------------------------------------- + +# The library detects link loss via onDeviceDisconnected. The app calls +# navigation.goBack(), returning to ScanScreen. +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 15000 + +# The scan-start-btn being visible confirms we are back on ScanScreen, +# indicating the app detected the disconnection and navigated back. + +# --------------------------------------------------------------------------- +# 4. Re-scan and reconnect after the peripheral is back up +# +# MANUAL / AUTOMATION STEP: +# If the peripheral was powered off, power it back on now before this step. +# --------------------------------------------------------------------------- +- tapOn: + id: "scan-start-btn" + +# Search field may still have text from before; clear and re-enter +- tapOn: + id: "scan-search" +- eraseText: 50 +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 20000 + +- tapOn: + id: "device-BlePlxTest" + +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 5. Clean disconnect +# --------------------------------------------------------------------------- +- tapOn: + id: "disconnect-btn" + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/indicate-stress.yaml b/integration-tests/hardware/maestro/indicate-stress.yaml new file mode 100644 index 000000000..43e4a6b67 --- /dev/null +++ b/integration-tests/hardware/maestro/indicate-stress.yaml @@ -0,0 +1,188 @@ +# Maestro flow: Indicate Stream Stress Test +# +# Verifies that the library correctly handles a sustained BLE indication stream +# over 30 seconds without dropping the connection, leaking memory, or crashing. +# +# The BlePlxTest peripheral sends indications on the Indicate Stream +# characteristic continuously while monitoring is active. +# This flow enables the monitor toggle, waits 30 seconds with periodic +# liveness checks, then disables monitoring and disconnects. +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on and advertising within range. +# - The test app is installed and foregrounded on the device under test. +# - BLE permissions have been pre-granted (see README.md). +# - The BlePlxTest firmware implements an indicatable characteristic that +# sends indications at a regular cadence when enabled. +# +# Run: +# maestro test integration-tests/hardware/maestro/indicate-stress.yaml + +appId: com.bleplxexample + +--- + +# --------------------------------------------------------------------------- +# 1. Launch and scan +# --------------------------------------------------------------------------- +- launchApp: + clearState: true + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 5000 + +- extendedWaitUntil: + visible: + id: "ble-state" + timeout: 10000 + +- tapOn: + id: "scan-start-btn" + +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 15000 + +# --------------------------------------------------------------------------- +# 2. Connect to BlePlxTest +# --------------------------------------------------------------------------- +- tapOn: + id: "device-BlePlxTest" + +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 3. Discover services +# --------------------------------------------------------------------------- +- tapOn: + id: "discover-btn" + +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 4. Navigate to the Indicate Stream characteristic +# --------------------------------------------------------------------------- +- tapOn: + id: "char-indicate-stream" + +- extendedWaitUntil: + visible: + id: "monitor-toggle" + timeout: 5000 + +# Confirm the value-display is present before enabling monitoring +- assertVisible: + id: "value-display" + +# --------------------------------------------------------------------------- +# 5. Enable the monitor +# --------------------------------------------------------------------------- +- tapOn: + id: "monitor-toggle" + +# Give a moment for the first indication to arrive +- waitForAnimationToEnd: + timeout: 2000 + +# Confirm the value-display is still visible and no error alert appeared +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# --------------------------------------------------------------------------- +# 6. Run for 30 seconds with periodic liveness checks +# --------------------------------------------------------------------------- + +# t ~ 5 s +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# t ~ 10 s +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# t ~ 15 s +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# t ~ 20 s +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# t ~ 25 s +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# t ~ 30 s — final check +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: + id: "value-display" +- assertNotVisible: + text: "Monitor error" + +# --------------------------------------------------------------------------- +# 7. Disable monitoring +# --------------------------------------------------------------------------- +- tapOn: + id: "monitor-toggle" + +- waitForAnimationToEnd: + timeout: 1000 + +# No error should appear after stopping +- assertNotVisible: + text: "Monitor error" + +# --------------------------------------------------------------------------- +# 8. Navigate back and disconnect cleanly +# --------------------------------------------------------------------------- +- back + +- extendedWaitUntil: + visible: + id: "disconnect-btn" + timeout: 5000 + +- tapOn: + id: "disconnect-btn" + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/run-e2e.sh b/integration-tests/hardware/maestro/run-e2e.sh new file mode 100755 index 000000000..29e5e5057 --- /dev/null +++ b/integration-tests/hardware/maestro/run-e2e.sh @@ -0,0 +1,248 @@ +#!/bin/bash +# +# E2E BLE Test Orchestrator +# +# Starts the BlePlxTest peripheral on one device and runs maestro-runner flows +# against the example app on the other device. +# +# Usage: +# ./run-e2e.sh android # Android=scanner, iPhone=BlePlxTest peripheral +# ./run-e2e.sh ios # iPhone=scanner, Android=BlePlxTest peripheral +# ./run-e2e.sh android 03 # Run only test 03 on Android +# +# Prerequisites: +# - Both devices connected (adb devices, xcrun devicectl list devices) +# - Apps installed on both devices +# - BLE permissions pre-granted +# - maestro-runner installed (~/.maestro-runner/bin/maestro-runner) +# - For iOS: pymobiledevice3 tunneld running (sudo pymobiledevice3 remote tunneld -d) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +# Device IDs +IPHONE_UDID="00008130-000A34C12021401C" +APPLE_TEAM_ID="2974F4A5QH" + +# App bundle IDs +EXAMPLE_ANDROID="com.bleplxexample" +EXAMPLE_IOS="com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-" +PERIPHERAL_ANDROID="com.bleplx.testperipheral" +PERIPHERAL_IOS="com.bleplx.testperipheral.ios" + +ADB="/Users/shan/Library/Android/sdk/platform-tools/adb" +MAESTRO_RUNNER="${HOME}/.maestro-runner/bin/maestro-runner" +export JAVA_HOME="${JAVA_HOME:-/Applications/Android Studio.app/Contents/jbr/Contents/Home}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[E2E]${NC} $*"; } +warn() { echo -e "${YELLOW}[E2E]${NC} $*"; } +fail() { echo -e "${RED}[E2E]${NC} $*"; exit 1; } + +# --- Preflight --- + +if [ ! -f "$MAESTRO_RUNNER" ]; then + fail "maestro-runner not found at $MAESTRO_RUNNER" +fi + +# --- Helper functions --- + +kill_android_app() { + $ADB shell am force-stop "$1" 2>/dev/null || true +} + +launch_android_app() { + $ADB shell am start -n "$1/.MainActivity" 2>/dev/null +} + +launch_ios_app() { + xcrun devicectl device process launch --terminate-existing --device "$IPHONE_UDID" "$1" 2>/dev/null +} + +pre_test_hook() { + local test_name="$1" + case "$test_name" in + 17-permissions-denied*) + if [ "$SCANNER" = "android" ]; then + log "Revoking BLE permissions for permission-denied test..." + $ADB shell pm revoke "$EXAMPLE_ANDROID" android.permission.BLUETOOTH_SCAN 2>/dev/null || true + $ADB shell pm revoke "$EXAMPLE_ANDROID" android.permission.BLUETOOTH_CONNECT 2>/dev/null || true + $ADB shell pm revoke "$EXAMPLE_ANDROID" android.permission.ACCESS_FINE_LOCATION 2>/dev/null || true + fi + ;; + esac +} + +post_test_hook() { + local test_name="$1" + case "$test_name" in + 17-permissions-denied*) + if [ "$SCANNER" = "android" ]; then + log "Restoring BLE permissions..." + $ADB shell pm grant "$EXAMPLE_ANDROID" android.permission.BLUETOOTH_SCAN 2>/dev/null || true + $ADB shell pm grant "$EXAMPLE_ANDROID" android.permission.BLUETOOTH_CONNECT 2>/dev/null || true + $ADB shell pm grant "$EXAMPLE_ANDROID" android.permission.ACCESS_FINE_LOCATION 2>/dev/null || true + fi + ;; + esac +} + +# --- Main --- + +SCANNER="${1:-}" +SPECIFIC_TEST="${2:-}" + +if [ -z "$SCANNER" ]; then + echo "Usage: $0 [test-number]" + echo "" + echo "Examples:" + echo " $0 android # Run all tests, Android as scanner" + echo " $0 ios # Run all tests, iOS as scanner" + echo " $0 android 03 # Run test 03 only, Android as scanner" + exit 1 +fi + +case "$SCANNER" in + android) + SCANNER_PLATFORM="android" + MAESTRO_PLATFORM_FLAGS="" + ;; + ios) + SCANNER_PLATFORM="ios" + MAESTRO_PLATFORM_FLAGS="--platform ios --team-id $APPLE_TEAM_ID" + ;; + *) + fail "Unknown scanner platform: $SCANNER (use 'android' or 'ios')" + ;; +esac + +log "Configuration: ${SCANNER_PLATFORM} = scanner, other = peripheral" +log "" + +# Step 1: Kill apps on both devices to start clean +log "Killing Android apps..." +kill_android_app "$EXAMPLE_ANDROID" +kill_android_app "$PERIPHERAL_ANDROID" +sleep 1 + +if [ "$SCANNER" = "ios" ]; then + log "Killing iOS apps..." + xcrun devicectl device process terminate --device "$IPHONE_UDID" "$EXAMPLE_IOS" 2>/dev/null || true + xcrun devicectl device process terminate --device "$IPHONE_UDID" "$PERIPHERAL_IOS" 2>/dev/null || true + sleep 1 +fi + +# Step 2: Launch the peripheral on the server device +log "Launching BlePlxTest peripheral..." +if [ "$SCANNER" = "android" ]; then + launch_ios_app "$PERIPHERAL_IOS" +else + launch_android_app "$PERIPHERAL_ANDROID" +fi + +log "Waiting 5s for peripheral to start advertising..." +sleep 5 + +# Step 3: Prepare flows +FLOW_DIR="$SCRIPT_DIR" +TMP_DIR="/tmp/maestro-e2e-$$" +mkdir -p "$TMP_DIR" + +# For iOS, swap appId in all flows +if [ "$SCANNER" = "ios" ]; then + for f in "$FLOW_DIR"/_*.yaml "$FLOW_DIR"/[0-9][0-9]-*.yaml; do + [ -f "$f" ] || continue + case "$f" in *-ios.yaml) continue;; esac + sed "s/appId: $EXAMPLE_ANDROID/appId: $EXAMPLE_IOS/" "$f" > "$TMP_DIR/$(basename "$f")" + done + # Use iOS-specific background test + if [ -f "$FLOW_DIR/10-background-mode-ios.yaml" ]; then + cp "$FLOW_DIR/10-background-mode-ios.yaml" "$TMP_DIR/10-background-mode.yaml" + fi + FLOW_DIR="$TMP_DIR" +fi + +# Step 4: Run tests +log "Running maestro-runner flows against ${SCANNER_PLATFORM}..." +log "" + +PASSED=0 +FAILED=0 +RESULTS="" + +if [ -n "$SPECIFIC_TEST" ]; then + FLOWS=("$FLOW_DIR/${SPECIFIC_TEST}"*.yaml) +else + FLOWS=("$FLOW_DIR"/[0-9][0-9]-*.yaml) +fi + +for flow in "${FLOWS[@]}"; do + [ -f "$flow" ] || continue + # Skip iOS-specific variant from main list + case "$(basename "$flow")" in *-ios.yaml) continue;; esac + + test_name=$(basename "$flow" .yaml) + + # Platform-skip for Android-only tests + case "$(basename "$flow")" in + 12-request-mtu*|17-permissions-denied*) + [ "$SCANNER" = "ios" ] && { warn "SKIP (Android-only): $test_name"; continue; } ;; + esac + + log "Running: $test_name" + + # Kill any lingering WDA port (iOS only) + if [ "$SCANNER" = "ios" ]; then + lsof -ti :8152 2>/dev/null | xargs kill -9 2>/dev/null || true + sleep 1 + fi + + pre_test_hook "$test_name" + + if $MAESTRO_RUNNER $MAESTRO_PLATFORM_FLAGS test "$flow" 2>&1 | grep -v '^time=' | grep -qE "✓ PASS"; then + echo -e " ${GREEN}PASS${NC}: $test_name" + RESULTS="${RESULTS}\n ${GREEN}PASS${NC}: $test_name" + PASSED=$((PASSED + 1)) + else + echo -e " ${RED}FAIL${NC}: $test_name" + RESULTS="${RESULTS}\n ${RED}FAIL${NC}: $test_name" + FAILED=$((FAILED + 1)) + fi + + post_test_hook "$test_name" + echo "" +done + +# Cleanup +rm -rf "$TMP_DIR" + +# Step 5: Summary +log "=========================================" +log "E2E Test Results (${SCANNER_PLATFORM} scanner)" +log "=========================================" +echo -e "$RESULTS" +echo "" +log "Passed: $PASSED Failed: $FAILED" +log "" + +warn "=========================================" +warn "Features NOT covered by E2E tests:" +warn "=========================================" +warn " - PHY Requests (Android-only, no UI effect)" +warn " - Connection Priority (Android-only, no UI effect)" +warn " - Bond State / Pairing (requires system dialog)" +warn " - State Restoration (requires app kill/relaunch)" +warn " - Multiple Simultaneous Connections (single peripheral)" +warn " - Transaction Cancellation (timing impractical in UI tests)" +warn " - Authorization Status (read-only, can't toggle)" +warn "" + +if [ "$FAILED" -gt 0 ]; then + exit 1 +fi diff --git a/integration-tests/hardware/maestro/scan-pair-sync.yaml b/integration-tests/hardware/maestro/scan-pair-sync.yaml new file mode 100644 index 000000000..dc133d065 --- /dev/null +++ b/integration-tests/hardware/maestro/scan-pair-sync.yaml @@ -0,0 +1,128 @@ +# Maestro flow: Scan → Pair → Sync (full happy path) +# +# Verifies the complete happy-path of discovering a BLE peripheral, connecting +# to it, discovering services, navigating to a readable characteristic, reading +# its value, and disconnecting cleanly. +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on and advertising within range. +# - The test app is installed and foregrounded on the device under test. +# - BLE permissions have been pre-granted (see README.md). +# - See integration-tests/hardware/peripheral-firmware/README.md for firmware +# requirements. +# +# Run: +# maestro test integration-tests/hardware/maestro/scan-pair-sync.yaml + +appId: com.bleplxexample + +--- + +# --------------------------------------------------------------------------- +# 1. Launch the app fresh +# --------------------------------------------------------------------------- +- launchApp: + clearState: true + +# Wait for the scan screen buttons to be visible +- assertVisible: + id: "scan-start-btn" + timeout: 5000 + +# --------------------------------------------------------------------------- +# 2. Wait for BLE to be powered on, then start scanning +# --------------------------------------------------------------------------- +- extendedWaitUntil: + visible: + id: "ble-state" + timeout: 10000 + +- tapOn: + id: "scan-start-btn" + +# --------------------------------------------------------------------------- +# 3. Filter and wait for the BlePlxTest peripheral to appear +# --------------------------------------------------------------------------- +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- assertVisible: + id: "device-list" + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 15000 + +# --------------------------------------------------------------------------- +# 4. Tap the device row to connect +# --------------------------------------------------------------------------- +- tapOn: + id: "device-BlePlxTest" + +# --------------------------------------------------------------------------- +# 5. Wait for the DeviceScreen to load +# --------------------------------------------------------------------------- +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 6. Discover services +# --------------------------------------------------------------------------- +- tapOn: + id: "discover-btn" + +# Wait for the test service to appear and auto-expand +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 7. Navigate to the Read Counter characteristic +# --------------------------------------------------------------------------- +- tapOn: + id: "char-read-counter" + +# --------------------------------------------------------------------------- +# 8. On CharacteristicScreen: read the value and verify it appears +# --------------------------------------------------------------------------- +- extendedWaitUntil: + visible: + id: "read-btn" + timeout: 5000 + +- tapOn: + id: "read-btn" + +# value-display starts as "(none)"; after a successful read it shows a base64 +# encoded value. Assert it is no longer showing "(none)". +- extendedWaitUntil: + visible: + id: "value-display" + timeout: 5000 +- assertNotVisible: + text: "Value: (none)" + +# --------------------------------------------------------------------------- +# 9. Navigate back and disconnect +# --------------------------------------------------------------------------- +- back + +- extendedWaitUntil: + visible: + id: "disconnect-btn" + timeout: 5000 + +- tapOn: + id: "disconnect-btn" + +# After disconnecting, navigation returns to ScanScreen +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/write-read-roundtrip.yaml b/integration-tests/hardware/maestro/write-read-roundtrip.yaml new file mode 100644 index 000000000..200d91330 --- /dev/null +++ b/integration-tests/hardware/maestro/write-read-roundtrip.yaml @@ -0,0 +1,134 @@ +# Maestro flow: Write → Read Roundtrip +# +# Verifies that a value written to the Write Echo characteristic can be read +# back with the same content, confirming bidirectional characteristic I/O works +# end-to-end. +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on and advertising within range. +# - The test app is installed and foregrounded on the device under test. +# - BLE permissions have been pre-granted (see README.md). +# - The BlePlxTest firmware implements a write-echo characteristic that echoes +# back whatever value was last written. +# +# Run: +# maestro test integration-tests/hardware/maestro/write-read-roundtrip.yaml + +appId: com.bleplxexample + +--- + +# --------------------------------------------------------------------------- +# 1. Launch the app and wait for BLE to be ready +# --------------------------------------------------------------------------- +- launchApp: + clearState: true + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 5000 + +- extendedWaitUntil: + visible: + id: "ble-state" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 2. Scan and connect to BlePlxTest +# --------------------------------------------------------------------------- +- tapOn: + id: "scan-start-btn" + +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" + timeout: 15000 + +- tapOn: + id: "device-BlePlxTest" + +- extendedWaitUntil: + visible: + id: "discover-btn" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 3. Discover services +# --------------------------------------------------------------------------- +- tapOn: + id: "discover-btn" + +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 4. Navigate to the Write Echo characteristic +# --------------------------------------------------------------------------- +- tapOn: + id: "char-write-echo" + +- extendedWaitUntil: + visible: + id: "write-input" + timeout: 5000 + +# --------------------------------------------------------------------------- +# 5. Write a test value +# +# "Hello" base64-encoded is "SGVsbG8=". The write-input accepts base64 strings. +# --------------------------------------------------------------------------- +- tapOn: + id: "write-input" +- eraseText: 50 +- inputText: "SGVsbG8=" + +- tapOn: + id: "write-btn" + +# Dismiss the "Value written successfully" alert +- extendedWaitUntil: + visible: + text: "Value written successfully" + timeout: 5000 +- tapOn: + text: "OK" + +# --------------------------------------------------------------------------- +# 6. Read back the value and verify it matches what was written +# --------------------------------------------------------------------------- +- tapOn: + id: "read-btn" + +# The value-display should now show the echoed base64 value +- extendedWaitUntil: + visible: + id: "value-display" + timeout: 5000 +- assertVisible: + text: "SGVsbG8=" + +# --------------------------------------------------------------------------- +# 7. Navigate back and disconnect +# --------------------------------------------------------------------------- +- back + +- extendedWaitUntil: + visible: + id: "disconnect-btn" + timeout: 5000 + +- tapOn: + id: "disconnect-btn" + +- extendedWaitUntil: + visible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/peripheral-firmware/README.md b/integration-tests/hardware/peripheral-firmware/README.md new file mode 100644 index 000000000..6ea2f5139 --- /dev/null +++ b/integration-tests/hardware/peripheral-firmware/README.md @@ -0,0 +1,56 @@ +# Peripheral Firmware for Hardware Integration Tests + +This directory holds firmware or firmware instructions for the BLE peripheral +device used during hardware-in-the-loop testing. + +## What is needed + +The hardware tests (Maestro flows) require a BLE peripheral that exposes at +minimum the following GATT profile: + +### Required services and characteristics + +| Service UUID | Characteristic UUID | Properties | Description | +|---|---|---|---| +| `0000180d-0000-1000-8000-00805f9b34fb` | `00002a37-0000-1000-8000-00805f9b34fb` | Read, Notify | Heart Rate Measurement (test notify flow) | +| `0000180d-0000-1000-8000-00805f9b34fb` | `00002a38-0000-1000-8000-00805f9b34fb` | Read | Body Sensor Location | +| `0000fff0-0000-1000-8000-00805f9b34fb` | `0000fff1-0000-1000-8000-00805f9b34fb` | Write With Response | Writable test characteristic | +| `0000fff0-0000-1000-8000-00805f9b34fb` | `0000fff2-0000-1000-8000-00805f9b34fb` | Indicate | Indicate stress-test characteristic | + +### Advertising + +- Device name: `BlePlxTest` (exactly — Maestro flows match on this name) +- Connectable: yes +- Manufacturer data: `0x4254 0x01 0x00` (BlePlx Test marker, version 1.0) + +## Supported firmware platforms + +The tests are designed to work with any of the following: + +- **nRF52840 DK / nRF52840 Dongle** running a Zephyr GATT sample + (recommended — widely available, USB-programmable) +- **ESP32** running an Arduino BLE sketch +- **Any iOS/Android device** running the nRF Connect app configured as a GATT + server with the profile above (no dedicated hardware required for initial + bringup) + +## Implementing notify / indicate + +The `0000fff2` indicate characteristic must send a new value every 250 ms when +a client enables indications. This exercises the indicate stress-test flow. + +The `00002a37` notify characteristic should cycle through dummy heart-rate +values (60–120 bpm) at 1 Hz. + +## Running the firmware + +Instructions depend on the chosen platform. See the nRF Connect SDK or ESP32 +Arduino BLE documentation for steps to flash and verify the peripheral is +advertising. + +Once the peripheral is advertising, run the Maestro flows from the `maestro/` +directory: + +```sh +maestro test integration-tests/hardware/maestro/scan-pair-sync.yaml +``` diff --git a/integration-tests/hardware/peripheral-firmware/ble_test_peripheral/ble_test_peripheral.ino b/integration-tests/hardware/peripheral-firmware/ble_test_peripheral/ble_test_peripheral.ino new file mode 100644 index 000000000..aa28958db --- /dev/null +++ b/integration-tests/hardware/peripheral-firmware/ble_test_peripheral/ble_test_peripheral.ino @@ -0,0 +1,507 @@ +// ble_test_peripheral.ino — BLE test peripheral for react-native-ble-plx integration tests +// +// Target: Seeed XIAO nRF52840 +// Platform: Adafruit nRF52 Arduino core (Bluefruit) +// +// GATT service exposes five characteristics covering the main BLE operations +// that react-native-ble-plx exercises: plain read, authenticated write, notify, +// indicate, and MTU negotiation. +// +// Serial interface (115200 baud) lets a test harness control the peripheral: +// status — print connection state, MTU, subscription flags +// disconnect — force-disconnect the current central +// set — set the echo characteristic value (hex bytes, no spaces) +// notify-rate — change the notify interval +// indicate-rate — change the indicate interval +// reset — clear bonds and restart BLE stack + +#include "config.h" +#include + +// ─── GATT Objects ─── + +static BLEService svc(TEST_SERVICE_UUID); +static BLECharacteristic chrReadCounter(CHAR_READ_COUNTER_UUID); +static BLECharacteristic chrWriteEcho(CHAR_WRITE_ECHO_UUID); +static BLECharacteristic chrNotify(CHAR_NOTIFY_UUID); +static BLECharacteristic chrIndicate(CHAR_INDICATE_UUID); +static BLECharacteristic chrMtu(CHAR_MTU_TEST_UUID); + +// ─── State ─── + +static bool g_connected = false; +static uint16_t g_conn_handle = BLE_CONN_HANDLE_INVALID; +static uint16_t g_mtu = 23; // default BLE 4.0 ATT MTU + +static uint32_t g_read_counter = 0; // increments on each read of chrReadCounter + +static uint8_t g_echo_buf[ECHO_MAX_LEN]; +static uint16_t g_echo_len = 0; + +static uint32_t g_notify_counter = 0; +static uint32_t g_indicate_counter = 0; +static bool g_notify_subscribed = false; +static bool g_indicate_subscribed = false; +static bool g_indicate_pending = false; // waiting for ATT-layer ACK + +static uint32_t g_notify_interval = NOTIFY_INTERVAL_MS; +static uint32_t g_indicate_interval = INDICATE_INTERVAL_MS; +static uint32_t g_last_notify_ms = 0; +static uint32_t g_last_indicate_ms = 0; + +// ─── Serial command buffer ─── +static char g_serial_buf[SERIAL_BUF_SIZE]; +static uint8_t g_serial_pos = 0; + +// ─── Forward Declarations ─── + +static void ble_init(); +static void ble_start_advertising(); +static void connect_callback(uint16_t conn_handle); +static void disconnect_callback(uint16_t conn_handle, uint8_t reason); +static void mtu_changed_callback(uint16_t conn_handle, uint16_t mtu); +static void notify_cccd_callback(uint16_t conn_handle, BLECharacteristic* chr, + uint16_t value); +static void indicate_cccd_callback(uint16_t conn_handle, BLECharacteristic* chr, + uint16_t value); +static void indicate_confirm_callback(uint16_t conn_handle, BLECharacteristic* chr); +static void echo_write_callback(uint16_t conn_handle, BLECharacteristic* chr, + uint8_t* data, uint16_t len); +static uint16_t read_counter_authorize_callback(uint16_t conn_handle, + BLECharacteristic* chr, + ble_gatts_evt_read_t* request); +static void process_serial_cmd(const char* cmd); +static void print_status(); + +// ─── Setup ─────────────────────────────────────────────────────────────────── + +void setup() { + Serial.begin(SERIAL_BAUD); + // Brief wait for host to open the serial port (USB CDC). + // Not required for normal operation — skip if no host is attached. + for (int i = 0; i < 20 && !Serial; i++) delay(50); + + Serial.println("=== BLE Test Peripheral ==="); + Serial.print("Device: "); + Serial.println(DEVICE_NAME); + Serial.print("Firmware: "); + Serial.println(FW_VERSION); + + // Connection LED: off at start + pinMode(LED_CONN, OUTPUT); + digitalWrite(LED_CONN, LED_OFF); + + // Initialize echo buffer with a default string + const char* default_echo = "echo"; + g_echo_len = strlen(default_echo); + memcpy(g_echo_buf, default_echo, g_echo_len); + + ble_init(); + ble_start_advertising(); + + Serial.println("[MAIN] Ready. Type 'status' for current state."); +} + +// ─── Loop ──────────────────────────────────────────────────────────────────── + +void loop() { + // ── Serial command processing ── + while (Serial.available()) { + char c = (char)Serial.read(); + if (c == '\n' || c == '\r') { + if (g_serial_pos > 0) { + g_serial_buf[g_serial_pos] = '\0'; + process_serial_cmd(g_serial_buf); + g_serial_pos = 0; + } + } else if (g_serial_pos < SERIAL_BUF_SIZE - 1) { + g_serial_buf[g_serial_pos++] = c; + } + } + + if (!g_connected) return; + + uint32_t now = millis(); + + // ── Notify stream ── + if (g_notify_subscribed && (now - g_last_notify_ms >= g_notify_interval)) { + g_last_notify_ms = now; + uint32_t val = g_notify_counter++; + if (!chrNotify.notify(g_conn_handle, (uint8_t*)&val, 4)) { + // Central may have unsubscribed or disconnected; not fatal + } + } + + // ── Indicate stream ── + // Only send when subscribed and not waiting for the previous ACK + if (g_indicate_subscribed && !g_indicate_pending && + (now - g_last_indicate_ms >= g_indicate_interval)) { + g_last_indicate_ms = now; + uint32_t val = g_indicate_counter++; + g_indicate_pending = true; + if (!chrIndicate.indicate(g_conn_handle, (uint8_t*)&val, 4)) { + g_indicate_pending = false; + } + } +} + +// ─── BLE Initialization ────────────────────────────────────────────────────── + +static void ble_init() { + Bluefruit.begin(); + Bluefruit.setName(DEVICE_NAME); + Bluefruit.setTxPower(4); + + // Accept up to MTU_MAX from the central + Bluefruit.setMaxMtu(MTU_MAX); + + // Connection / disconnection callbacks + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // MTU negotiation result callback + Bluefruit.setMtuCallback(mtu_changed_callback); + + // ── Security: support Just Works pairing, persist bonds ── + // NoInputNoOutput means Just Works (no passkey display or entry). + Bluefruit.Security.setIOCaps(false, false, false); + Bluefruit.Security.setMITM(false); + // Auto-accept pairing requests from the central + Bluefruit.Security.setAutoAcceptPairing(true); + + // ── GATT Service ── + svc.begin(); + + // ── Read Counter (Read, open) ── + // Uses a read-authorize callback so we can increment the counter on each read + // and return the updated value atomically. + chrReadCounter.setProperties(CHR_PROPS_READ); + chrReadCounter.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + chrReadCounter.setFixedLen(4); + chrReadCounter.setReadAuthorizeCallback(read_counter_authorize_callback); + chrReadCounter.begin(); + uint32_t init_counter = 0; + chrReadCounter.write((uint8_t*)&init_counter, 4); + + // ── Write Echo (Read + Write, bonded / encrypted) ── + // Writes require encryption (bonding). Reads are also encrypted. + chrWriteEcho.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); + chrWriteEcho.setPermission(SECMODE_ENC_NO_MITM, SECMODE_ENC_NO_MITM); + chrWriteEcho.setMaxLen(ECHO_MAX_LEN); + chrWriteEcho.setWriteCallback(echo_write_callback); + chrWriteEcho.begin(); + chrWriteEcho.write(g_echo_buf, g_echo_len); + + // ── Notify Stream (Notify, open) ── + chrNotify.setProperties(CHR_PROPS_NOTIFY); + chrNotify.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + chrNotify.setFixedLen(4); + chrNotify.setCccdWriteCallback(notify_cccd_callback); + chrNotify.begin(); + + // ── Indicate Stream (Indicate, open) ── + chrIndicate.setProperties(CHR_PROPS_INDICATE); + chrIndicate.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + chrIndicate.setFixedLen(4); + chrIndicate.setCccdWriteCallback(indicate_cccd_callback); + chrIndicate.setIndicateCallback(indicate_confirm_callback); + chrIndicate.begin(); + + // ── MTU Test (Read, open) ── + chrMtu.setProperties(CHR_PROPS_READ); + chrMtu.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + chrMtu.setFixedLen(2); + chrMtu.begin(); + uint16_t init_mtu = 23; + chrMtu.write((uint8_t*)&init_mtu, 2); + + Serial.println("[BLE] GATT service initialized"); +} + +static void ble_start_advertising() { + Bluefruit.Advertising.clearData(); + Bluefruit.ScanResponse.clearData(); + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addService(svc); + + // Full device name in scan response (may not fit in the 31-byte ADV payload) + Bluefruit.ScanResponse.addName(); + + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(ADV_FAST_INTERVAL, ADV_SLOW_INTERVAL); + Bluefruit.Advertising.setFastTimeout(ADV_FAST_TIMEOUT_SEC); + Bluefruit.Advertising.start(0); // 0 = advertise indefinitely + + Serial.println("[BLE] Advertising started"); +} + +// ─── Connection Callbacks ───────────────────────────────────────────────────── + +static void connect_callback(uint16_t conn_handle) { + g_connected = true; + g_conn_handle = conn_handle; + + // Reset stream counters and subscription state for the new connection + g_notify_counter = 0; + g_indicate_counter = 0; + g_notify_subscribed = false; + g_indicate_subscribed = false; + g_indicate_pending = false; + g_last_notify_ms = millis(); + g_last_indicate_ms = millis(); + + // Update MTU characteristic with the post-connection default (23) until + // the MTU exchange completes and mtu_changed_callback fires. + g_mtu = 23; + chrMtu.write((uint8_t*)&g_mtu, 2); + + digitalWrite(LED_CONN, LED_ON); + + Serial.print("[BLE] Connected, handle="); + Serial.println(conn_handle); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + g_connected = false; + g_conn_handle = BLE_CONN_HANDLE_INVALID; + + g_notify_subscribed = false; + g_indicate_subscribed = false; + g_indicate_pending = false; + + digitalWrite(LED_CONN, LED_OFF); + + Serial.print("[BLE] Disconnected, reason=0x"); + Serial.println(reason, HEX); +} + +static void mtu_changed_callback(uint16_t conn_handle, uint16_t mtu) { + g_mtu = mtu; + // Update the MTU Test characteristic so the central can read back the + // actual negotiated MTU. + chrMtu.write((uint8_t*)&mtu, 2); + + Serial.print("[BLE] MTU updated: "); + Serial.println(mtu); +} + +// ─── CCCD Callbacks ────────────────────────────────────────────────────────── + +static void notify_cccd_callback(uint16_t conn_handle, BLECharacteristic* chr, + uint16_t value) { + g_notify_subscribed = (value == BLE_GATT_HVX_NOTIFICATION); + g_notify_counter = 0; + g_last_notify_ms = millis(); + + Serial.print("[BLE] Notify subscribed: "); + Serial.println(g_notify_subscribed ? "YES" : "NO"); +} + +static void indicate_cccd_callback(uint16_t conn_handle, BLECharacteristic* chr, + uint16_t value) { + g_indicate_subscribed = (value == BLE_GATT_HVX_INDICATION); + g_indicate_counter = 0; + g_indicate_pending = false; + g_last_indicate_ms = millis(); + + Serial.print("[BLE] Indicate subscribed: "); + Serial.println(g_indicate_subscribed ? "YES" : "NO"); +} + +// ─── Indicate ACK Callback ─────────────────────────────────────────────────── + +static void indicate_confirm_callback(uint16_t conn_handle, BLECharacteristic* chr) { + // ATT-layer confirmation received — clear the pending flag so the next + // value can be sent in the next loop iteration. + g_indicate_pending = false; +} + +// ─── Write Echo Callback ───────────────────────────────────────────────────── + +static void echo_write_callback(uint16_t conn_handle, BLECharacteristic* chr, + uint8_t* data, uint16_t len) { + if (len == 0 || len > ECHO_MAX_LEN) { + Serial.print("[BLE] Echo write: invalid length "); + Serial.println(len); + return; + } + + memcpy(g_echo_buf, data, len); + g_echo_len = len; + + // Persist in the characteristic so subsequent reads return the new value + chrWriteEcho.write(g_echo_buf, g_echo_len); + + Serial.print("[BLE] Echo write: "); + Serial.print(len); + Serial.println(" bytes"); +} + +// ─── Read Counter Authorize Callback ───────────────────────────────────────── +// The Bluefruit read-authorize callback lets us mutate the characteristic value +// before the stack sends the response. We increment the counter here so each +// read returns a distinct value. + +static uint16_t read_counter_authorize_callback(uint16_t conn_handle, + BLECharacteristic* chr, + ble_gatts_evt_read_t* request) { + // Increment and write back so the ATT response includes the new value + g_read_counter++; + chrReadCounter.write((uint8_t*)&g_read_counter, 4); + + Serial.print("[BLE] Read counter -> "); + Serial.println(g_read_counter); + + // Return 0 to accept the read (stack will send the updated value) + return 0; +} + +// ─── Serial Command Processing ─────────────────────────────────────────────── + +static void print_status() { + Serial.println("--- Status ---"); + Serial.print("Connected: "); + Serial.println(g_connected ? "YES" : "NO"); + if (g_connected) { + Serial.print("Conn handle: "); + Serial.println(g_conn_handle); + Serial.print("MTU: "); + Serial.println(g_mtu); + } + Serial.print("Notify subscribed: "); + Serial.println(g_notify_subscribed ? "YES" : "NO"); + Serial.print("Indicate subscribed:"); + Serial.println(g_indicate_subscribed ? "YES" : "NO"); + Serial.print("Notify interval: "); + Serial.print(g_notify_interval); + Serial.println(" ms"); + Serial.print("Indicate interval:"); + Serial.print(g_indicate_interval); + Serial.println(" ms"); + Serial.print("Read counter: "); + Serial.println(g_read_counter); + Serial.print("Echo length: "); + Serial.println(g_echo_len); + Serial.println("--------------"); +} + +// Parse a hex string (no spaces) into a byte buffer. +// Returns the number of bytes written, or 0 on error. +static uint8_t hex_to_bytes(const char* hex, uint8_t* out, uint8_t out_max) { + uint8_t len = strlen(hex); + if (len == 0 || len % 2 != 0) return 0; + uint8_t count = len / 2; + if (count > out_max) count = out_max; + for (uint8_t i = 0; i < count; i++) { + char hi = hex[i * 2]; + char lo = hex[i * 2 + 1]; + // Convert hex nibble to value + auto nibble = [](char c) -> int8_t { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; + }; + int8_t h = nibble(hi); + int8_t l = nibble(lo); + if (h < 0 || l < 0) return 0; + out[i] = (uint8_t)((h << 4) | l); + } + return count; +} + +static void process_serial_cmd(const char* cmd) { + Serial.print("[CMD] "); + Serial.println(cmd); + + // ── status ── + if (strcmp(cmd, "status") == 0) { + print_status(); + return; + } + + // ── disconnect ── + if (strcmp(cmd, "disconnect") == 0) { + if (!g_connected) { + Serial.println("[CMD] Not connected"); + return; + } + BLEConnection* conn = Bluefruit.Connection(g_conn_handle); + if (conn) conn->disconnect(); + Serial.println("[CMD] Disconnect requested"); + return; + } + + // ── reset ── + if (strcmp(cmd, "reset") == 0) { + Serial.println("[CMD] Clearing bonds and restarting BLE..."); + if (g_connected) { + BLEConnection* conn = Bluefruit.Connection(g_conn_handle); + if (conn) conn->disconnect(); + delay(200); + } + Bluefruit.clearBonds(); + delay(100); + Bluefruit.Advertising.stop(); + delay(100); + ble_start_advertising(); + Serial.println("[CMD] BLE restarted"); + return; + } + + // ── set ── + if (strncmp(cmd, "set ", 4) == 0) { + const char* hex = cmd + 4; + uint8_t tmp[ECHO_MAX_LEN]; + uint8_t n = hex_to_bytes(hex, tmp, ECHO_MAX_LEN); + if (n == 0) { + Serial.println("[CMD] set: invalid hex (must be even number of hex digits)"); + return; + } + memcpy(g_echo_buf, tmp, n); + g_echo_len = n; + chrWriteEcho.write(g_echo_buf, g_echo_len); + Serial.print("[CMD] Echo set to "); + Serial.print(n); + Serial.println(" bytes"); + return; + } + + // ── notify-rate ── + if (strncmp(cmd, "notify-rate ", 12) == 0) { + long ms = atol(cmd + 12); + if (ms < 10 || ms > 60000) { + Serial.println("[CMD] notify-rate: value must be 10-60000 ms"); + return; + } + g_notify_interval = (uint32_t)ms; + Serial.print("[CMD] Notify interval -> "); + Serial.print(g_notify_interval); + Serial.println(" ms"); + return; + } + + // ── indicate-rate ── + if (strncmp(cmd, "indicate-rate ", 14) == 0) { + long ms = atol(cmd + 14); + if (ms < 10 || ms > 60000) { + Serial.println("[CMD] indicate-rate: value must be 10-60000 ms"); + return; + } + g_indicate_interval = (uint32_t)ms; + Serial.print("[CMD] Indicate interval -> "); + Serial.print(g_indicate_interval); + Serial.println(" ms"); + return; + } + + Serial.println("[CMD] Unknown command. Available:"); + Serial.println(" status"); + Serial.println(" disconnect"); + Serial.println(" set "); + Serial.println(" notify-rate "); + Serial.println(" indicate-rate "); + Serial.println(" reset"); +} diff --git a/integration-tests/hardware/peripheral-firmware/ble_test_peripheral/config.h b/integration-tests/hardware/peripheral-firmware/ble_test_peripheral/config.h new file mode 100644 index 000000000..4bfda85fb --- /dev/null +++ b/integration-tests/hardware/peripheral-firmware/ble_test_peripheral/config.h @@ -0,0 +1,67 @@ +// config.h — BLE test peripheral configuration for react-native-ble-plx integration tests +// Target: Seeed XIAO nRF52840 +// Platform: Adafruit nRF52 Arduino core (Bluefruit) +#ifndef CONFIG_H +#define CONFIG_H + +// ─── Device Identity ─── +#define DEVICE_NAME "BlePlxTest" +#define FW_VERSION "1.0.0" + +// ─── Test Service UUID ─── +#define TEST_SERVICE_UUID "12345678-1234-1234-1234-123456789abc" + +// ─── Characteristic UUIDs ─── +// Read Counter: returns uint32_t that increments on each read +#define CHAR_READ_COUNTER_UUID "12345678-1234-1234-1234-123456789a01" +// Write Echo: Read+Write; stores written bytes and returns them on read +// Requires bonding (LESC_MITM) +#define CHAR_WRITE_ECHO_UUID "12345678-1234-1234-1234-123456789a02" +// Notify Stream: sends incrementing uint32_t every NOTIFY_INTERVAL_MS when subscribed +#define CHAR_NOTIFY_UUID "12345678-1234-1234-1234-123456789a03" +// Indicate Stream: sends incrementing uint32_t every INDICATE_INTERVAL_MS when subscribed +// Waits for ATT-layer ACK before sending next value +#define CHAR_INDICATE_UUID "12345678-1234-1234-1234-123456789a04" +// MTU Test: returns current negotiated MTU as uint16_t (little-endian) +#define CHAR_MTU_TEST_UUID "12345678-1234-1234-1234-123456789a05" + +// ─── Timing Defaults ─── +#define NOTIFY_INTERVAL_MS 500 // ms between notify packets when subscribed +#define INDICATE_INTERVAL_MS 1000 // ms between indicate packets when subscribed + +// ─── Advertising ─── +#define ADV_FAST_INTERVAL 160 // 100ms in 0.625ms units (160 * 0.625 = 100ms) +#define ADV_SLOW_INTERVAL 244 // ~152ms in 0.625ms units +#define ADV_FAST_TIMEOUT_SEC 30 // seconds in fast mode before switching to slow + +// ─── MTU ─── +#define MTU_MAX 517 // Maximum MTU to request/accept + +// ─── Serial ─── +#define SERIAL_BAUD 115200 +#define SERIAL_BUF_SIZE 128 // bytes for incoming serial command buffer + +// ─── Write Echo Max Length ─── +// ATT MTU - 3 bytes overhead, rounded to a round number +#define ECHO_MAX_LEN 512 + +// ─── Connection LED ─── +// XIAO nRF52840 has a built-in RGB LED; use the blue pin as "connected" indicator. +// On Adafruit core for XIAO nRF52840, LED_BUILTIN or LED_BLUE may be available. +// If neither resolves, define a safe fallback (PIN_LED1 is the blue LED on the XIAO). +#ifndef LED_CONN + #if defined(LED_BLUE) + #define LED_CONN LED_BLUE + #elif defined(LED_BUILTIN) + #define LED_CONN LED_BUILTIN + #else + #define LED_CONN 3 // P0.03 — blue LED on Seeed XIAO nRF52840 + #endif +#endif + +// ─── LED polarity ─── +// Most nRF52 boards drive LEDs active-LOW (LOW = on) +#define LED_ON LOW +#define LED_OFF HIGH + +#endif // CONFIG_H diff --git a/integration-tests/hardware/test-app/README.md b/integration-tests/hardware/test-app/README.md new file mode 100644 index 000000000..3cd950072 --- /dev/null +++ b/integration-tests/hardware/test-app/README.md @@ -0,0 +1,70 @@ +# Hardware Test App + +This directory documents the React Native test application used to drive +hardware integration tests via Maestro. + +## Overview + +The hardware tests require a test app installed on a physical iOS or Android +device. The app wraps `react-native-ble-plx` and exposes screen elements that +Maestro can target (by testID / accessibility label). + +## Option 1: Use the existing example app + +The `example/` directory at the repo root contains a full sample app. +You can install it on a device and point the Maestro flows at it. + +1. Build and install the example app: + ```sh + # iOS + cd example/ios && pod install && xcodebuild -scheme BlePlxExample -destination 'id=' + + # Android + cd example/android && ./gradlew installDebug + ``` + +2. The bundle ID / package name for Maestro: + - iOS: `com.bleplxexample` + - Android: `com.bleplxexample` + +## Option 2: Create a minimal test harness app + +For more targeted control, create a minimal app with screens that map 1:1 +to the Maestro flow steps: + +- **ScanScreen** — a "Start Scan" button and a list that shows discovered + devices. Each list item has `testID="device-{address}"`. +- **ConnectScreen** — shown after tapping a device. Shows connection state, + an MTU label, and a "Disconnect" button. +- **CharacteristicScreen** — shows the last received notification value + in a `Text` element with `testID="char-value"`. + +## testID conventions used by Maestro flows + +| testID | Usage | +|---|---| +| `start-scan-button` | Tap to begin BLE scan | +| `device-AA:BB:CC:DD:EE:FF` | Tap to connect to specific device | +| `connection-state-label` | Text element showing current state | +| `char-value` | Last received characteristic notification value | +| `write-button` | Tap to write a test value | +| `disconnect-button` | Tap to disconnect | +| `error-label` | Shown when a BleError is received | + +## Running against a physical device + +Ensure: +1. Bluetooth is enabled on the test phone. +2. The peripheral firmware device is powered on and advertising as `BlePlxTest`. +3. The test app is installed and foregrounded. + +Then run: +```sh +maestro test integration-tests/hardware/maestro/scan-pair-sync.yaml +``` + +## CI considerations + +Hardware tests are not run in CI by default because they require physical +devices. Gate them with a separate workflow trigger (`workflow_dispatch` with +`run_hardware_tests: true`) or a dedicated device-farm job. diff --git a/integration-tests/simulated/js/fullSync.test.ts b/integration-tests/simulated/js/fullSync.test.ts new file mode 100644 index 000000000..4b46de2e6 --- /dev/null +++ b/integration-tests/simulated/js/fullSync.test.ts @@ -0,0 +1,615 @@ +// integration-tests/simulated/js/fullSync.test.ts +// +// Full JS-layer flow tests with a mocked TurboModule. +// No real Bluetooth hardware required — all native calls are intercepted. +// +// Run with a dedicated jest config that includes this directory, +// since the root jest.config.js intentionally excludes integration-tests/. + +// --------------------------------------------------------------------------- +// Mock setup — must come before any imports that pull in react-native +// --------------------------------------------------------------------------- + +let scanResultHandlers: Array<(result: any) => void> = [] +let connectionStateHandlers: Array<(event: any) => void> = [] +let charValueHandlers: Array<(event: any) => void> = [] + +const mockNativeModule = { + createClient: jest.fn().mockResolvedValue(undefined), + destroyClient: jest.fn().mockResolvedValue(undefined), + state: jest.fn().mockResolvedValue('PoweredOn'), + startDeviceScan: jest.fn(), + stopDeviceScan: jest.fn().mockResolvedValue(undefined), + connectToDevice: jest.fn().mockResolvedValue({ + id: 'AA:BB:CC:DD:EE:FF', + name: 'SmartCap', + rssi: -55, + mtu: 247, + isConnectable: true, + serviceUuids: ['180D', '1800'], + manufacturerData: null + }), + cancelDeviceConnection: jest.fn().mockResolvedValue({ + id: 'AA:BB:CC:DD:EE:FF', + name: 'SmartCap', + rssi: -55, + mtu: 247, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + isDeviceConnected: jest.fn().mockResolvedValue(true), + discoverAllServicesAndCharacteristics: jest.fn().mockResolvedValue({ + id: 'AA:BB:CC:DD:EE:FF', + name: 'SmartCap', + rssi: -55, + mtu: 247, + isConnectable: true, + serviceUuids: ['180D'], + manufacturerData: null + }), + readCharacteristic: jest.fn().mockResolvedValue({ + deviceId: 'AA:BB:CC:DD:EE:FF', + serviceUuid: '180D', + uuid: '2A37', + value: 'AQID', + isNotifying: false, + isIndicatable: false, + isReadable: true, + isWritableWithResponse: false, + isWritableWithoutResponse: false + }), + writeCharacteristic: jest.fn().mockResolvedValue({ + deviceId: 'AA:BB:CC:DD:EE:FF', + serviceUuid: '180D', + uuid: '2A37', + value: 'BAUG', + isNotifying: false, + isIndicatable: false, + isReadable: true, + isWritableWithResponse: true, + isWritableWithoutResponse: false + }), + monitorCharacteristic: jest.fn(), + requestMtu: jest.fn().mockResolvedValue({ + id: 'AA:BB:CC:DD:EE:FF', + name: 'SmartCap', + rssi: -55, + mtu: 247, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + requestConnectionPriority: jest.fn().mockResolvedValue({ + id: 'AA:BB:CC:DD:EE:FF', + name: 'SmartCap', + rssi: -55, + mtu: 23, + isConnectable: true, + serviceUuids: [], + manufacturerData: null + }), + cancelTransaction: jest.fn().mockResolvedValue(undefined), + getMtu: jest.fn().mockResolvedValue(247), + requestPhy: jest.fn().mockResolvedValue({ + deviceId: 'AA:BB:CC:DD:EE:FF', + txPhy: 2, + rxPhy: 2 + }), + readPhy: jest.fn().mockResolvedValue({ + deviceId: 'AA:BB:CC:DD:EE:FF', + txPhy: 1, + rxPhy: 1 + }), + getBondedDevices: jest.fn().mockResolvedValue([]), + getAuthorizationStatus: jest.fn().mockResolvedValue('granted'), + openL2CAPChannel: jest.fn().mockResolvedValue({ channelId: 5, deviceId: 'AA:BB:CC:DD:EE:FF', psm: 128 }), + writeL2CAPChannel: jest.fn().mockResolvedValue(undefined), + closeL2CAPChannel: jest.fn().mockResolvedValue(undefined), + + // Event emitters + onScanResult: jest.fn(handler => { + scanResultHandlers.push(handler) + return { + remove: jest.fn(() => { + scanResultHandlers = scanResultHandlers.filter(h => h !== handler) + }) + } + }), + onConnectionStateChange: jest.fn(handler => { + connectionStateHandlers.push(handler) + return { + remove: jest.fn(() => { + connectionStateHandlers = connectionStateHandlers.filter(h => h !== handler) + }) + } + }), + onCharacteristicValueUpdate: jest.fn(handler => { + charValueHandlers.push(handler) + return { + remove: jest.fn(() => { + charValueHandlers = charValueHandlers.filter(h => h !== handler) + }) + } + }), + onStateChange: jest.fn(() => ({ + remove: jest.fn() + })), + onError: jest.fn(() => ({ remove: jest.fn() })), + onRestoreState: jest.fn(() => ({ remove: jest.fn() })), + onBondStateChange: jest.fn(() => ({ remove: jest.fn() })), + onConnectionEvent: jest.fn(() => ({ remove: jest.fn() })) +} + +jest.mock('react-native', () => ({ + TurboModuleRegistry: { + get: jest.fn(() => mockNativeModule) + }, + Platform: { + OS: 'ios' + } +})) + +// --------------------------------------------------------------------------- +// Imports — after jest.mock() hoisting +// --------------------------------------------------------------------------- + +import { BleManager } from '../../../src/BleManager' + +import { BleError, BleErrorCode } from '../../../src/BleError' + +// --------------------------------------------------------------------------- +// Constants used throughout tests +// --------------------------------------------------------------------------- + +const DEVICE_ID = 'AA:BB:CC:DD:EE:FF' +const SERVICE_UUID = '180D' +const CHAR_UUID = '2A37' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function makeScanResult(id = DEVICE_ID) { + return { + id, + name: 'SmartCap', + rssi: -55, + serviceUuids: ['180D'], + manufacturerData: null + } +} + +function makeCharEvent(txId: string, deviceId = DEVICE_ID, svc = SERVICE_UUID, char = CHAR_UUID) { + return { + deviceId, + serviceUuid: svc, + characteristicUuid: char, + value: 'AQID', + transactionId: txId + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('Full JS flow with mocked native module', () => { + let manager: BleManager + + beforeEach(() => { + jest.clearAllMocks() + scanResultHandlers = [] + connectionStateHandlers = [] + charValueHandlers = [] + + manager = new BleManager({ scanBatchIntervalMs: 0 }) + }) + + afterEach(async () => { + await manager.destroyClient().catch(() => {}) + }) + + // ------------------------------------------------------------------------- + // Step 1: createClient → state = PoweredOn + // ------------------------------------------------------------------------- + + test('step 1: createClient initialises native and state() returns PoweredOn', async () => { + await manager.createClient() + + expect(mockNativeModule.createClient).toHaveBeenCalledWith(null) + + const state = await manager.state() + expect(state).toBe('PoweredOn') + expect(mockNativeModule.state).toHaveBeenCalled() + }) + + // ------------------------------------------------------------------------- + // Step 2: startDeviceScan → onScanResult fires with mock device + // ------------------------------------------------------------------------- + + test('step 2: startDeviceScan fires onScanResult callback with scanned device', () => { + const received: any[] = [] + + manager.startDeviceScan(null, null, (err, device) => { + expect(err).toBeNull() + if (device) received.push(device) + }) + + expect(mockNativeModule.startDeviceScan).toHaveBeenCalledWith(null, null) + expect(mockNativeModule.onScanResult).toHaveBeenCalled() + + // Simulate native delivering a scan result + const handler = scanResultHandlers[scanResultHandlers.length - 1]! + handler(makeScanResult()) + + expect(received).toHaveLength(1) + expect(received[0].id).toBe(DEVICE_ID) + expect(received[0].name).toBe('SmartCap') + expect(received[0].rssi).toBe(-55) + }) + + test('step 2b: startDeviceScan with service UUID filter passes filter to native', () => { + manager.startDeviceScan(['180D'], null, jest.fn()) + + expect(mockNativeModule.startDeviceScan).toHaveBeenCalledWith(['180D'], null) + }) + + // ------------------------------------------------------------------------- + // Step 3: connectToDevice → resolves with DeviceInfo + // ------------------------------------------------------------------------- + + test('step 3: connectToDevice resolves with DeviceInfo', async () => { + await manager.createClient() + const device = await manager.connectToDevice(DEVICE_ID) + + expect(mockNativeModule.connectToDevice).toHaveBeenCalledWith(DEVICE_ID, null) + expect(device.id).toBe(DEVICE_ID) + expect(device.name).toBe('SmartCap') + expect(device.mtu).toBe(247) + }) + + test('step 3b: connectToDevice forwards options to native', async () => { + await manager.createClient() + const options = { autoConnect: false, timeout: 10000, requestMtu: 512 } + await manager.connectToDevice(DEVICE_ID, options) + + expect(mockNativeModule.connectToDevice).toHaveBeenCalledWith(DEVICE_ID, options) + }) + + // ------------------------------------------------------------------------- + // Step 4: writeCharacteristicForDevice → resolves with CharacteristicInfo + // ------------------------------------------------------------------------- + + test('step 4: writeCharacteristicForDevice resolves with characteristic info', async () => { + await manager.createClient() + + const result = await manager.writeCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, 'BAUG', true) + + expect(mockNativeModule.writeCharacteristic).toHaveBeenCalledWith( + DEVICE_ID, + SERVICE_UUID, + CHAR_UUID, + 'BAUG', + true, + null + ) + expect(result.value).toBe('BAUG') + expect(result.deviceId).toBe(DEVICE_ID) + expect(result.isWritableWithResponse).toBe(true) + }) + + test('step 4b: writeCharacteristicForDevice without response forwards withResponse=false', async () => { + await manager.createClient() + + await manager.writeCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, 'AA==', false) + + const call = mockNativeModule.writeCharacteristic.mock.calls[0] as any[] + expect(call[4]).toBe(false) + }) + + // ------------------------------------------------------------------------- + // Step 5: monitorCharacteristicForDevice → onCharacteristicValueUpdate fires + // ------------------------------------------------------------------------- + + test('step 5: monitorCharacteristicForDevice routes characteristic value events', async () => { + await manager.createClient() + + const received: any[] = [] + const sub = manager.monitorCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, (err, event) => { + expect(err).toBeNull() + if (event) received.push(event) + }) + + expect(mockNativeModule.monitorCharacteristic).toHaveBeenCalled() + const txId = (mockNativeModule.monitorCharacteristic.mock.calls[0] as any[])[4] + expect(txId).toMatch(/^__ble_tx_\d+$/) + + const charHandler = charValueHandlers[charValueHandlers.length - 1]! + + // Matching event + charHandler(makeCharEvent(txId)) + // Another matching event + charHandler(makeCharEvent(txId)) + + expect(received).toHaveLength(2) + expect(received[0].deviceId).toBe(DEVICE_ID) + expect(received[0].value).toBe('AQID') + + sub.remove() + }) + + // ------------------------------------------------------------------------- + // Step 6 (audit #3 fix verification): + // subscription.remove() stops native monitor AND removes JS listener + // ------------------------------------------------------------------------- + + test('step 6: subscription.remove() cancels native transaction AND removes JS listener', async () => { + await manager.createClient() + + const received: any[] = [] + const sub = manager.monitorCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, (err, event) => { + if (event) received.push(event) + }) + + const txId = (mockNativeModule.monitorCharacteristic.mock.calls[0] as any[])[4] + + // Remove the subscription + sub.remove() + + // cancelTransaction must have been called with the correct txId + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId) + + // JS listener should be removed — charValueHandlers should no longer include this handler + expect(charValueHandlers).toHaveLength(0) + + // Any events arriving after remove() must not call the listener + // (Simulate: try to fire — if any handler were still registered it would add to received) + // charValueHandlers is empty so nothing fires — this is the expected behaviour + + expect(received).toHaveLength(0) + }) + + test('step 6b: subscription.remove() is idempotent — second call is a no-op', async () => { + await manager.createClient() + + const sub = manager.monitorCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, jest.fn()) + + sub.remove() + const cancelCallCount = mockNativeModule.cancelTransaction.mock.calls.length + + // Second remove() should not call cancelTransaction again + sub.remove() + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledTimes(cancelCallCount) + }) + + // ------------------------------------------------------------------------- + // Step 7: cancelDeviceConnection → onConnectionStateChange fires + // ------------------------------------------------------------------------- + + test('step 7: cancelDeviceConnection resolves and native fires disconnected event', async () => { + await manager.createClient() + + const disconnectEvents: any[] = [] + manager.onDeviceDisconnected(DEVICE_ID, (err, event) => { + expect(err).toBeNull() + if (event) disconnectEvents.push(event) + }) + + // Perform the disconnect + const result = await manager.cancelDeviceConnection(DEVICE_ID) + expect(result.id).toBe(DEVICE_ID) + expect(mockNativeModule.cancelDeviceConnection).toHaveBeenCalledWith(DEVICE_ID) + + // Simulate native firing the disconnection event + connectionStateHandlers.forEach(h => + h({ + deviceId: DEVICE_ID, + state: 'disconnected', + errorCode: null, + errorMessage: null + }) + ) + + expect(disconnectEvents).toHaveLength(1) + expect(disconnectEvents[0].state).toBe('disconnected') + }) + + // ------------------------------------------------------------------------- + // Error scenarios + // ------------------------------------------------------------------------- + + test('error scenario: permission denied rejects connectToDevice', async () => { + await manager.createClient() + + const permError = new BleError({ + code: BleErrorCode.BluetoothUnauthorized, + message: 'Bluetooth permission denied', + isRetryable: false, + platform: 'ios' + }) + + mockNativeModule.connectToDevice.mockRejectedValueOnce(permError) + + await expect(manager.connectToDevice(DEVICE_ID)).rejects.toMatchObject({ + code: BleErrorCode.BluetoothUnauthorized, + message: 'Bluetooth permission denied' + }) + }) + + test('error scenario: connection timeout rejects connectToDevice', async () => { + await manager.createClient() + + const timeoutError = new BleError({ + code: BleErrorCode.ConnectionTimeout, + message: 'Connection timed out', + isRetryable: true, + platform: 'ios', + deviceId: DEVICE_ID + }) + + mockNativeModule.connectToDevice.mockRejectedValueOnce(timeoutError) + + const caught = await manager.connectToDevice(DEVICE_ID).catch(e => e) + expect(caught).toBeInstanceOf(BleError) + expect(caught.code).toBe(BleErrorCode.ConnectionTimeout) + expect(caught.isRetryable).toBe(true) + }) + + test('error scenario: GATT error rejects writeCharacteristicForDevice', async () => { + await manager.createClient() + + const gattError = new BleError({ + code: BleErrorCode.CharacteristicWriteFailed, + message: 'GATT write failed: status 133', + isRetryable: false, + platform: 'android', + deviceId: DEVICE_ID, + serviceUuid: SERVICE_UUID, + characteristicUuid: CHAR_UUID, + gattStatus: 133 + }) + + mockNativeModule.writeCharacteristic.mockRejectedValueOnce(gattError) + + const caught = await manager + .writeCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, 'AQID', true) + .catch(e => e) + + expect(caught).toBeInstanceOf(BleError) + expect(caught.code).toBe(BleErrorCode.CharacteristicWriteFailed) + expect(caught.gattStatus).toBe(133) + expect(caught.serviceUUID).toBe(SERVICE_UUID) + expect(caught.characteristicUUID).toBe(CHAR_UUID) + }) + + test('error scenario: disconnected with error code calls onDeviceDisconnected with BleError', async () => { + await manager.createClient() + + const errorCallback = jest.fn() + manager.onDeviceDisconnected(DEVICE_ID, errorCallback) + + connectionStateHandlers.forEach(h => + h({ + deviceId: DEVICE_ID, + state: 'disconnected', + errorCode: BleErrorCode.ConnectionFailed, + errorMessage: 'GATT connection failed' + }) + ) + + expect(errorCallback).toHaveBeenCalledTimes(1) + const [err, event] = errorCallback.mock.calls[0] as [BleError, any] + expect(err).toBeInstanceOf(BleError) + expect(err.code).toBe(BleErrorCode.ConnectionFailed) + expect(err.message).toBe('GATT connection failed') + expect(event).not.toBeNull() + }) + + // ------------------------------------------------------------------------- + // Additional coverage + // ------------------------------------------------------------------------- + + test('onStateChange with emitCurrentState delivers PoweredOn immediately', async () => { + const states: string[] = [] + const sub = manager.onStateChange(s => states.push(s), true) + + // Microtask queue resolves the state() promise + await Promise.resolve() + + expect(states).toContain('PoweredOn') + + sub.remove() + }) + + test('stopDeviceScan cleans up subscription and calls native', async () => { + const received: any[] = [] + manager.startDeviceScan(null, null, (err, device) => { + if (device) received.push(device) + }) + + const subRemoveSpy = jest.spyOn(mockNativeModule.onScanResult.mock.results[0].value, 'remove') + + await manager.stopDeviceScan() + + expect(mockNativeModule.stopDeviceScan).toHaveBeenCalled() + expect(subRemoveSpy).toHaveBeenCalled() + expect(scanResultHandlers).toHaveLength(0) + }) + + test('isDeviceConnected returns true', async () => { + await manager.createClient() + const connected = await manager.isDeviceConnected(DEVICE_ID) + expect(connected).toBe(true) + expect(mockNativeModule.isDeviceConnected).toHaveBeenCalledWith(DEVICE_ID) + }) + + test('discoverAllServicesAndCharacteristics resolves with DeviceInfo', async () => { + await manager.createClient() + const result = await manager.discoverAllServicesAndCharacteristics(DEVICE_ID) + expect(result.id).toBe(DEVICE_ID) + expect(mockNativeModule.discoverAllServicesAndCharacteristics).toHaveBeenCalledWith(DEVICE_ID, null) + }) + + test('destroyClient cancels all active monitor transactions', async () => { + await manager.createClient() + + manager.monitorCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, jest.fn()) + manager.monitorCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, '2A38', jest.fn()) + + const txId1 = (mockNativeModule.monitorCharacteristic.mock.calls[0] as any[])[4] + const txId2 = (mockNativeModule.monitorCharacteristic.mock.calls[1] as any[])[4] + + await manager.destroyClient() + + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId1) + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId2) + expect(mockNativeModule.destroyClient).toHaveBeenCalled() + }) + + test('full happy-path flow: connect → discover → write → monitor → disconnect', async () => { + // 1. Init + await manager.createClient() + expect(await manager.state()).toBe('PoweredOn') + + // 2. Scan + const scanned: any[] = [] + manager.startDeviceScan(null, null, (err, device) => { + if (device) scanned.push(device) + }) + const scanHandler = scanResultHandlers[scanResultHandlers.length - 1]! + scanHandler(makeScanResult()) + expect(scanned).toHaveLength(1) + + await manager.stopDeviceScan() + + // 3. Connect + const deviceInfo = await manager.connectToDevice(DEVICE_ID) + expect(deviceInfo.id).toBe(DEVICE_ID) + + // 4. Discover + await manager.discoverAllServicesAndCharacteristics(DEVICE_ID) + + // 5. Write + const writeResult = await manager.writeCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, 'BAUG', true) + expect(writeResult.value).toBe('BAUG') + + // 6. Monitor + const notifications: any[] = [] + const sub = manager.monitorCharacteristicForDevice(DEVICE_ID, SERVICE_UUID, CHAR_UUID, (err, event) => { + if (event) notifications.push(event) + }) + const txId = (mockNativeModule.monitorCharacteristic.mock.calls[0] as any[])[4] + const charHandler = charValueHandlers[charValueHandlers.length - 1]! + charHandler(makeCharEvent(txId)) + expect(notifications).toHaveLength(1) + + // 7. Stop monitoring + sub.remove() + expect(mockNativeModule.cancelTransaction).toHaveBeenCalledWith(txId) + + // 8. Disconnect + const disconnectResult = await manager.cancelDeviceConnection(DEVICE_ID) + expect(disconnectResult.id).toBe(DEVICE_ID) + }) +}) diff --git a/ios/BLEActor.swift b/ios/BLEActor.swift new file mode 100644 index 000000000..f97ccdddf --- /dev/null +++ b/ios/BLEActor.swift @@ -0,0 +1,449 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - BLEActor + +/// Central manager actor with custom executor pinned to the CoreBluetooth queue. +/// All CBCentralManager and CBPeripheral interactions happen on this queue. +actor BLEActor { + let queue = DispatchSerialQueue(label: "com.bleplx.ble") + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + private var centralManager: CBCentralManager! + let delegateHandler: CentralManagerDelegate + private var peripherals: [UUID: PeripheralWrapper] = [:] + private var scanManager: ScanManager + private var stateRestoration: StateRestoration + private var l2capManager: L2CAPManager! + + // Notification monitoring tasks + private var monitorTasks: [String: Task] = [:] + // Disconnection monitoring task + private var disconnectionTask: Task? + // State monitoring task + private var stateTask: Task? + // Restoration task + private var restorationTask: Task? + + // Event callbacks + private let onScanResult: @Sendable (ScanResultSnapshot) -> Void + private let onConnectionStateChange: @Sendable (String, String, BleError?) -> Void + private let onCharacteristicValueUpdate: @Sendable (String, String, String, String?, String?) -> Void + private let onStateChange: @Sendable (String) -> Void + private let onRestoreState: @Sendable ([PeripheralSnapshot]) -> Void + private let onError: @Sendable (BleError) -> Void + private let onL2CAPData: @Sendable (Int, String) -> Void + private let onL2CAPClose: @Sendable (Int, String?) -> Void + + init( + onScanResult: @escaping @Sendable (ScanResultSnapshot) -> Void, + onConnectionStateChange: @escaping @Sendable (String, String, BleError?) -> Void, + onCharacteristicValueUpdate: @escaping @Sendable (String, String, String, String?, String?) -> Void, + onStateChange: @escaping @Sendable (String) -> Void, + onRestoreState: @escaping @Sendable ([PeripheralSnapshot]) -> Void, + onError: @escaping @Sendable (BleError) -> Void, + onL2CAPData: @escaping @Sendable (Int, String) -> Void, + onL2CAPClose: @escaping @Sendable (Int, String?) -> Void + ) { + self.delegateHandler = CentralManagerDelegate() + self.scanManager = ScanManager(queue: queue) + self.stateRestoration = StateRestoration(queue: queue) + self.onScanResult = onScanResult + self.onConnectionStateChange = onConnectionStateChange + self.onCharacteristicValueUpdate = onCharacteristicValueUpdate + self.onStateChange = onStateChange + self.onRestoreState = onRestoreState + self.onError = onError + self.onL2CAPData = onL2CAPData + self.onL2CAPClose = onL2CAPClose + + self.l2capManager = L2CAPManager( + onData: onL2CAPData, + onClose: onL2CAPClose + ) + } + + // MARK: - Lifecycle + + func createClient(restoreStateIdentifier: String?) { + var options: [String: Any] = [:] + if let identifier = restoreStateIdentifier { + options[CBCentralManagerOptionRestoreIdentifierKey] = identifier + } + + centralManager = CBCentralManager( + delegate: delegateHandler, + queue: queue, + options: options.isEmpty ? nil : options + ) + + // Monitor state changes + stateTask = Task { [weak self, delegateHandler] in + for await state in delegateHandler.stateStream { + guard let self = self else { break } + let stateStr = EventSerializer.stateString(from: state) + self.onStateChange(stateStr) + } + } + + // Monitor disconnections + disconnectionTask = Task { [weak self, delegateHandler] in + for await result in delegateHandler.disconnectionStream { + guard let self = self else { break } + let deviceId = result.peripheralId.uuidString + let error = result.error.map { + ErrorConverter.from(cbError: $0, deviceId: deviceId, operation: "disconnect") + } + self.onConnectionStateChange(deviceId, "disconnected", error) + + // Clean up the peripheral wrapper + await self.removePeripheral(result.peripheralId) + } + } + + // Monitor restoration + restorationTask = Task { [weak self, delegateHandler] in + for await _ in delegateHandler.restorationStream { + guard let self = self else { break } + // Consume the raw dicts from the delegate (same CB queue — no Sendable crossing). + let rawDicts = delegateHandler.consumeRawRestorationDicts() + for dict in rawDicts { + let wrappers = await self.stateRestoration.handleRestoration(dict: dict) { peripheral in + PeripheralWrapper(peripheral: peripheral, queue: self.queue) + } + for wrapper in wrappers { + let uuid = UUID(uuidString: wrapper.deviceId)! + await self.storePeripheral(uuid, wrapper: wrapper) + } + } + let snapshots = await self.stateRestoration.getRestoredDeviceInfos() + self.onRestoreState(snapshots) + } + } + } + + func destroyClient() async { + // Stop scanning + if let cm = centralManager { + await scanManager.cleanup(centralManager: cm) + } + + // Cancel all monitor tasks + for (_, task) in monitorTasks { + task.cancel() + } + monitorTasks.removeAll() + + // Clean up peripherals + for (_, wrapper) in peripherals { + await wrapper.cleanup() + } + peripherals.removeAll() + + // Clean up L2CAP + await l2capManager.cleanup() + + // Clean up restoration + await stateRestoration.cleanup() + + // Cancel monitoring tasks + stateTask?.cancel() + disconnectionTask?.cancel() + restorationTask?.cancel() + + // Finish delegate streams + delegateHandler.finish() + + centralManager = nil + } + + // MARK: - State + + func state() -> String { + guard let cm = centralManager else { + return "Unknown" + } + return EventSerializer.stateString(from: cm.state) + } + + // MARK: - Scanning + + func startDeviceScan(serviceUuids: [CBUUID]?, options: [String: Any]?) { + guard let cm = centralManager else { return } + + var cbOptions: [String: Any] = [:] + if let allowDuplicates = options?["allowDuplicates"] as? Bool, allowDuplicates { + cbOptions[CBCentralManagerScanOptionAllowDuplicatesKey] = true + } + + Task { + await scanManager.startScan( + centralManager: cm, + serviceUuids: serviceUuids, + options: cbOptions.isEmpty ? nil : cbOptions, + delegateHandler: delegateHandler, + onResult: onScanResult + ) + } + } + + func stopDeviceScan() async { + guard let cm = centralManager else { return } + await scanManager.stopScan(centralManager: cm) + } + + // MARK: - Connection + + func connectToDevice(deviceId: String, options: [String: Any]?) async throws -> PeripheralSnapshot { + guard let cm = centralManager else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + + guard let uuid = UUID(uuidString: deviceId) else { + throw BleError(code: .invalidIdentifiers, message: "Invalid device ID: \(deviceId)") + } + + // Check if already connected + if let existing = peripherals[uuid], await existing.isConnected() { + throw BleError(code: .deviceAlreadyConnected, message: "Device \(deviceId) is already connected", deviceId: deviceId) + } + + // Find or retrieve the peripheral + let peripheral: CBPeripheral + if let wrapper = peripherals[uuid] { + peripheral = await wrapper.cbPeripheral + } else if let discovered = delegateHandler.discoveredPeripheral(for: uuid) { + peripheral = discovered + } else { + let known = cm.retrievePeripherals(withIdentifiers: [uuid]) + guard let p = known.first else { + throw BleError(code: .deviceNotFound, message: "Device \(deviceId) not found", deviceId: deviceId) + } + peripheral = p + } + + // Emit connecting state + onConnectionStateChange(deviceId, "connecting", nil) + + // Build connect options + var connectOptions: [String: Any]? = nil + if let opts = options { + var cbOpts: [String: Any] = [:] + if let autoConnect = opts["autoConnect"] as? Bool, !autoConnect { + // iOS doesn't support autoConnect=false, it always auto-connects + } + if !cbOpts.isEmpty { + connectOptions = cbOpts + } + } + + // Connect with continuation + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegateHandler.addConnectionContinuation(continuation, for: uuid) + cm.connect(peripheral, options: connectOptions) + } + + // Create wrapper and store + let wrapper = PeripheralWrapper(peripheral: peripheral, queue: queue) + peripherals[uuid] = wrapper + + // Emit connected state + onConnectionStateChange(deviceId, "connected", nil) + + return await wrapper.snapshot() + } + + func cancelDeviceConnection(deviceId: String) async throws -> PeripheralSnapshot { + guard let cm = centralManager else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + + guard let uuid = UUID(uuidString: deviceId) else { + throw BleError(code: .invalidIdentifiers, message: "Invalid device ID: \(deviceId)") + } + + guard let wrapper = peripherals[uuid] else { + throw BleError(code: .deviceNotFound, message: "Device \(deviceId) not found", deviceId: deviceId) + } + + let snapshot = await wrapper.snapshot() + + onConnectionStateChange(deviceId, "disconnecting", nil) + cm.cancelPeripheralConnection(await wrapper.cbPeripheral) + + // Cleanup happens via the disconnection delegate callback + return snapshot + } + + func isDeviceConnected(deviceId: String) async throws -> Bool { + guard let uuid = UUID(uuidString: deviceId) else { + throw BleError(code: .invalidIdentifiers, message: "Invalid device ID: \(deviceId)") + } + + guard let wrapper = peripherals[uuid] else { + return false + } + + return await wrapper.isConnected() + } + + // MARK: - Discovery + + func discoverAllServicesAndCharacteristics(deviceId: String) async throws -> PeripheralSnapshot { + let wrapper = try getPeripheral(deviceId: deviceId) + return try await wrapper.discoverAllServicesAndCharacteristics() + } + + // MARK: - Read/Write + + func readCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + transactionId: String? + ) async throws -> CharacteristicSnapshot { + let wrapper = try getPeripheral(deviceId: deviceId) + return try await wrapper.readCharacteristic( + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + transactionId: transactionId + ) + } + + func writeCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + value: Data, + withResponse: Bool, + transactionId: String? + ) async throws -> CharacteristicSnapshot { + let wrapper = try getPeripheral(deviceId: deviceId) + return try await wrapper.writeCharacteristic( + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + value: value, + withResponse: withResponse, + transactionId: transactionId + ) + } + + // MARK: - Monitor + + func monitorCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + transactionId: String? + ) async throws { + let wrapper = try getPeripheral(deviceId: deviceId) + + // Enable notifications + try await wrapper.setNotifyValue(true, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid) + + let monitorKey = "\(deviceId)|\(serviceUuid)|\(characteristicUuid)" + + // Cancel any existing monitor for this characteristic + monitorTasks[monitorKey]?.cancel() + + // Forward notification values + let onUpdate = onCharacteristicValueUpdate + monitorTasks[monitorKey] = Task { + for await update in await wrapper.characteristicUpdateStream { + guard !Task.isCancelled else { break } + if update.characteristicUuid == characteristicUuid && + update.serviceUuid == serviceUuid { + onUpdate(deviceId, serviceUuid, characteristicUuid, update.value, transactionId) + } + } + } + } + + func stopMonitorCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String + ) async throws { + let wrapper = try getPeripheral(deviceId: deviceId) + try await wrapper.setNotifyValue(false, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid) + + let monitorKey = "\(deviceId)|\(serviceUuid)|\(characteristicUuid)" + monitorTasks[monitorKey]?.cancel() + monitorTasks.removeValue(forKey: monitorKey) + } + + // MARK: - MTU + + func getMtu(deviceId: String) async throws -> Int { + let wrapper = try getPeripheral(deviceId: deviceId) + return await wrapper.mtu() + } + + func requestMtu(deviceId: String, mtu: Int) async throws -> PeripheralSnapshot { + // iOS doesn't support requesting a specific MTU — it's negotiated automatically + let wrapper = try getPeripheral(deviceId: deviceId) + return await wrapper.snapshot() + } + + // MARK: - RSSI + + func readRSSI(deviceId: String) async throws -> Int { + let wrapper = try getPeripheral(deviceId: deviceId) + return try await wrapper.readRSSI() + } + + // MARK: - L2CAP + + func openL2CAPChannel(deviceId: String, psm: UInt16) async throws -> Int { + let wrapper = try getPeripheral(deviceId: deviceId) + let channel = try await wrapper.openL2CAPChannel(psm: CBL2CAPPSM(psm)) + do { + let channelId = await l2capManager.registerChannel(channel) + return channelId + } catch { + channel.inputStream.close() + channel.outputStream.close() + throw error + } + } + + func writeL2CAPChannel(channelId: Int, data: Data) async throws { + try await l2capManager.write(channelId: channelId, data: data) + } + + func closeL2CAPChannel(channelId: Int) async throws { + try await l2capManager.close(channelId: channelId) + } + + // MARK: - Cancel Transaction + + func cancelTransaction(_ transactionId: String) async { + for (_, wrapper) in peripherals { + await wrapper.cancelTransaction(transactionId) + } + } + + // MARK: - Private helpers + + private func getPeripheral(deviceId: String) throws -> PeripheralWrapper { + guard let uuid = UUID(uuidString: deviceId) else { + throw BleError(code: .invalidIdentifiers, message: "Invalid device ID: \(deviceId)") + } + + guard let wrapper = peripherals[uuid] else { + throw BleError(code: .deviceNotConnected, message: "Device \(deviceId) is not connected", deviceId: deviceId) + } + + return wrapper + } + + private func storePeripheral(_ uuid: UUID, wrapper: PeripheralWrapper) { + peripherals[uuid] = wrapper + } + + private func removePeripheral(_ uuid: UUID) async { + if let wrapper = peripherals.removeValue(forKey: uuid) { + await wrapper.cleanup() + } + } +} diff --git a/ios/BLEModuleImpl.swift b/ios/BLEModuleImpl.swift new file mode 100644 index 000000000..35896d4ec --- /dev/null +++ b/ios/BLEModuleImpl.swift @@ -0,0 +1,542 @@ +import Foundation +@preconcurrency import CoreBluetooth + +/// Protocol for the event emitter (ObjC++ BlePlx module) +/// Uses typed emit methods matching the Codegen-generated NativeBlePlxSpecBase +@objc public protocol BLEEventEmitter: AnyObject { + func emitOnScanResult(_ value: NSDictionary) + func emitOnConnectionStateChange(_ value: NSDictionary) + func emitOnCharacteristicValueUpdate(_ value: NSDictionary) + func emitOnStateChange(_ value: NSDictionary) + func emitOnRestoreState(_ value: NSDictionary) + func emitOnError(_ value: NSDictionary) + func emitOnBondStateChange(_ value: NSDictionary) + func emitOnConnectionEvent(_ value: NSDictionary) + func emitOnL2CAPData(_ value: NSDictionary) + func emitOnL2CAPClose(_ value: NSDictionary) +} + +// MARK: - BLEModuleImpl + +/// @objc Swift class that bridges between the ObjC++ TurboModule entry and the Swift actor. +/// All methods are called from the ObjC++ layer and delegate to BLEActor. +@objc public class BLEModuleImpl: NSObject { + + private weak var eventEmitter: BLEEventEmitter? + private var actor: BLEActor? + + @objc public init(eventEmitter: BLEEventEmitter) { + self.eventEmitter = eventEmitter + super.init() + } + + @objc public static func supportedEventNames() -> [String] { + return [ + "onScanResult", + "onConnectionStateChange", + "onCharacteristicValueUpdate", + "onStateChange", + "onRestoreState", + "onError", + "onBondStateChange", + "onConnectionEvent", + "onL2CAPData", + "onL2CAPClose", + ] + } + + // MARK: - Private helpers + + private func emitError(_ dict: [String: Any]) { + eventEmitter?.emitOnError(dict as NSDictionary) + } + + private func rejectWithError(_ reject: @escaping @Sendable (String?, String?, Error?) -> Void, error: BleError) { + reject(String(error.code.rawValue), error.message, nil) + emitError(error.toDictionary()) + } + + private func rejectWithError(_ reject: @escaping @Sendable (String?, String?, Error?) -> Void, error: Error) { + if let bleError = error as? BleError { + rejectWithError(reject, error: bleError) + } else { + let bleError = BleError(code: .unknown, message: error.localizedDescription) + rejectWithError(reject, error: bleError) + } + } + + // MARK: - Lifecycle + + @objc public func createClient( + restoreStateIdentifier: String?, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + let emitter = self.eventEmitter + + actor = BLEActor( + onScanResult: { [weak emitter] snapshot in + emitter?.emitOnScanResult(snapshot.toDictionary() as NSDictionary) + }, + onConnectionStateChange: { [weak emitter] deviceId, state, error in + var body: [String: Any] = [ + "deviceId": deviceId, + "state": state, + ] + body["errorCode"] = error?.code.rawValue + body["errorMessage"] = error?.message + emitter?.emitOnConnectionStateChange(body as NSDictionary) + }, + onCharacteristicValueUpdate: { [weak emitter] deviceId, serviceUuid, charUuid, value, transactionId in + var body: [String: Any] = [ + "deviceId": deviceId, + "serviceUuid": serviceUuid, + "characteristicUuid": charUuid, + "value": value ?? "", + ] + body["transactionId"] = transactionId + emitter?.emitOnCharacteristicValueUpdate(body as NSDictionary) + }, + onStateChange: { [weak emitter] state in + emitter?.emitOnStateChange(["state": state] as NSDictionary) + }, + onRestoreState: { [weak emitter] devices in + let deviceDicts = devices.map { $0.toDictionary() } + emitter?.emitOnRestoreState(["devices": deviceDicts] as NSDictionary) + }, + onError: { [weak emitter] error in + emitter?.emitOnError(error.toDictionary() as NSDictionary) + }, + onL2CAPData: { [weak emitter] channelId, data in + emitter?.emitOnL2CAPData(["channelId": channelId, "data": data] as NSDictionary) + }, + onL2CAPClose: { [weak emitter] channelId, error in + emitter?.emitOnL2CAPClose(["channelId": channelId, "error": error as Any] as NSDictionary) + } + ) + + Task { + await actor?.createClient(restoreStateIdentifier: restoreStateIdentifier) + resolve(nil) + } + } + + @objc public func destroyClient( + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + await actor?.destroyClient() + actor = nil + resolve(nil) + } + } + + @objc public func invalidate() { + Task { + await actor?.destroyClient() + actor = nil + } + } + + // MARK: - State + + @objc public func state( + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + let state = await actor?.state() ?? "Unknown" + resolve(state) + } + } + + // MARK: - Scanning + + @objc public func startDeviceScan( + uuids: [String]?, + options: NSDictionary? + ) { + let serviceUuids = uuids?.compactMap { CBUUID(string: $0) } + let opts = options as? [String: Any] + + Task { + await actor?.startDeviceScan(serviceUuids: serviceUuids, options: opts) + } + } + + @objc public func stopDeviceScan( + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + await actor?.stopDeviceScan() + resolve(nil) + } + } + + // MARK: - Connection + + @objc public func connectToDevice( + deviceId: String, + options: NSDictionary?, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + let opts = options as? [String: Any] + + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let snapshot = try await actor.connectToDevice(deviceId: deviceId, options: opts) + resolve(snapshot.toDictionary()) + } catch { + rejectWithError(reject, error: error) + } + } + } + + @objc public func cancelDeviceConnection( + deviceId: String, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let snapshot = try await actor.cancelDeviceConnection(deviceId: deviceId) + resolve(snapshot.toDictionary()) + } catch { + rejectWithError(reject, error: error) + } + } + } + + @objc public func isDeviceConnected( + deviceId: String, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let connected = try await actor.isDeviceConnected(deviceId: deviceId) + resolve(connected) + } catch { + rejectWithError(reject, error: error) + } + } + } + + // MARK: - Discovery + + @objc public func discoverAllServicesAndCharacteristics( + deviceId: String, + transactionId: String?, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let snapshot = try await actor.discoverAllServicesAndCharacteristics(deviceId: deviceId) + resolve(snapshot.toDictionary()) + } catch { + rejectWithError(reject, error: error) + } + } + } + + // MARK: - Read/Write + + @objc public func readCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + transactionId: String?, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let snapshot = try await actor.readCharacteristic( + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + transactionId: transactionId + ) + resolve(snapshot.toDictionary()) + } catch { + rejectWithError(reject, error: error) + } + } + } + + @objc public func writeCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + value: String, + withResponse: Bool, + transactionId: String?, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + guard let data = Data(base64Encoded: value) else { + throw BleError(code: .invalidIdentifiers, message: "Invalid base64 value") + } + let snapshot = try await actor.writeCharacteristic( + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + value: data, + withResponse: withResponse, + transactionId: transactionId + ) + resolve(snapshot.toDictionary()) + } catch { + rejectWithError(reject, error: error) + } + } + } + + // MARK: - Monitor + + @objc public func monitorCharacteristic( + deviceId: String, + serviceUuid: String, + characteristicUuid: String, + subscriptionType: String?, + transactionId: String? + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + try await actor.monitorCharacteristic( + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + transactionId: transactionId + ) + } catch { + if let bleError = error as? BleError { + emitError(bleError.toDictionary()) + } + } + } + } + + // MARK: - MTU + + @objc public func getMtu( + deviceId: String, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let mtu = try await actor.getMtu(deviceId: deviceId) + resolve(mtu) + } catch { + rejectWithError(reject, error: error) + } + } + } + + @objc public func requestMtu( + deviceId: String, + mtu: NSInteger, + transactionId: String?, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let snapshot = try await actor.requestMtu(deviceId: deviceId, mtu: mtu) + resolve(snapshot.toDictionary()) + } catch { + rejectWithError(reject, error: error) + } + } + } + + // MARK: - PHY (Not supported on iOS) + + @objc public func requestPhy( + deviceId: String, + txPhy: NSInteger, + rxPhy: NSInteger, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + // PHY selection is not available on iOS + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let wrapper = try await actor.discoverAllServicesAndCharacteristics(deviceId: deviceId) + resolve(wrapper.toDictionary()) + } catch { + reject(String(BleErrorCode.operationStartFailed.rawValue), + "PHY selection is not supported on iOS", nil) + } + } + } + + @objc public func readPhy( + deviceId: String, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + reject(String(BleErrorCode.operationStartFailed.rawValue), + "PHY reading is not supported on iOS", nil) + } + + // MARK: - Connection Priority (Not applicable on iOS) + + @objc public func requestConnectionPriority( + deviceId: String, + priority: NSInteger, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + // Connection priority is an Android-only concept + reject(String(BleErrorCode.operationStartFailed.rawValue), + "Connection priority is not supported on iOS", nil) + } + + // MARK: - L2CAP + + @objc public func openL2CAPChannel( + deviceId: String, + psm: NSInteger, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + let channelId = try await actor.openL2CAPChannel(deviceId: deviceId, psm: UInt16(psm)) + resolve(["channelId": channelId]) + } catch { + rejectWithError(reject, error: error) + } + } + } + + @objc public func writeL2CAPChannel( + channelId: NSInteger, + data: String, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + guard let binaryData = Data(base64Encoded: data) else { + throw BleError(code: .invalidIdentifiers, message: "Invalid base64 data") + } + try await actor.writeL2CAPChannel(channelId: channelId, data: binaryData) + resolve(nil) + } catch { + rejectWithError(reject, error: error) + } + } + } + + @objc public func closeL2CAPChannel( + channelId: NSInteger, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + do { + guard let actor = actor else { + throw BleError(code: .bluetoothManagerDestroyed, message: "BLE manager not initialized") + } + try await actor.closeL2CAPChannel(channelId: channelId) + resolve(nil) + } catch { + rejectWithError(reject, error: error) + } + } + } + + // MARK: - Bonding (Limited on iOS) + + @objc public func getBondedDevices( + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + // iOS doesn't expose bonded device list via CoreBluetooth + resolve([]) + } + + // MARK: - Authorization + + @objc public func getAuthorizationStatus( + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + if #available(iOS 13.1, *) { + switch CBManager.authorization { + case .allowedAlways: + resolve("authorized") + case .denied: + resolve("denied") + case .restricted: + resolve("restricted") + case .notDetermined: + resolve("notDetermined") + @unknown default: + resolve("notDetermined") + } + } else { + // Before iOS 13.1, BLE was always authorized if the app had the permission + resolve("authorized") + } + } + + // MARK: - Cancellation + + @objc public func cancelTransaction( + transactionId: String, + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void + ) { + Task { + await actor?.cancelTransaction(transactionId) + resolve(nil) + } + } +} + diff --git a/ios/BlePlx-Bridging-Header.h b/ios/BlePlx-Bridging-Header.h index 1b2cb5d6d..37eaae0fb 100644 --- a/ios/BlePlx-Bridging-Header.h +++ b/ios/BlePlx-Bridging-Header.h @@ -2,3 +2,6 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +#import +#import +#import diff --git a/ios/BlePlx-Swift.h b/ios/BlePlx-Swift.h deleted file mode 100644 index 20882ee39..000000000 --- a/ios/BlePlx-Swift.h +++ /dev/null @@ -1,444 +0,0 @@ -// Generated by Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100) -#ifndef MULTIPLATFORMBLEADAPTER_SWIFT_H -#define MULTIPLATFORMBLEADAPTER_SWIFT_H -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgcc-compat" - -#if !defined(__has_include) -# define __has_include(x) 0 -#endif -#if !defined(__has_attribute) -# define __has_attribute(x) 0 -#endif -#if !defined(__has_feature) -# define __has_feature(x) 0 -#endif -#if !defined(__has_warning) -# define __has_warning(x) 0 -#endif - -#if __has_include() -# include -#endif - -#pragma clang diagnostic ignored "-Wauto-import" -#if defined(__OBJC__) -#include -#endif -#if defined(__cplusplus) -#include -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#include -#endif -#if defined(__cplusplus) -#if __has_include() -# include -#else -# ifndef __ptrauth_swift_value_witness_function_pointer -# define __ptrauth_swift_value_witness_function_pointer(x) -# endif -#endif -#endif - -#if !defined(SWIFT_TYPEDEFS) -# define SWIFT_TYPEDEFS 1 -# if __has_include() -# include -# elif !defined(__cplusplus) -typedef uint_least16_t char16_t; -typedef uint_least32_t char32_t; -# endif -typedef float swift_float2 __attribute__((__ext_vector_type__(2))); -typedef float swift_float3 __attribute__((__ext_vector_type__(3))); -typedef float swift_float4 __attribute__((__ext_vector_type__(4))); -typedef double swift_double2 __attribute__((__ext_vector_type__(2))); -typedef double swift_double3 __attribute__((__ext_vector_type__(3))); -typedef double swift_double4 __attribute__((__ext_vector_type__(4))); -typedef int swift_int2 __attribute__((__ext_vector_type__(2))); -typedef int swift_int3 __attribute__((__ext_vector_type__(3))); -typedef int swift_int4 __attribute__((__ext_vector_type__(4))); -typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); -typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); -typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); -#endif - -#if !defined(SWIFT_PASTE) -# define SWIFT_PASTE_HELPER(x, y) x##y -# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) -#endif -#if !defined(SWIFT_METATYPE) -# define SWIFT_METATYPE(X) Class -#endif -#if !defined(SWIFT_CLASS_PROPERTY) -# if __has_feature(objc_class_property) -# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ -# else -# define SWIFT_CLASS_PROPERTY(...) -# endif -#endif -#if !defined(SWIFT_RUNTIME_NAME) -# if __has_attribute(objc_runtime_name) -# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) -# else -# define SWIFT_RUNTIME_NAME(X) -# endif -#endif -#if !defined(SWIFT_COMPILE_NAME) -# if __has_attribute(swift_name) -# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) -# else -# define SWIFT_COMPILE_NAME(X) -# endif -#endif -#if !defined(SWIFT_METHOD_FAMILY) -# if __has_attribute(objc_method_family) -# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) -# else -# define SWIFT_METHOD_FAMILY(X) -# endif -#endif -#if !defined(SWIFT_NOESCAPE) -# if __has_attribute(noescape) -# define SWIFT_NOESCAPE __attribute__((noescape)) -# else -# define SWIFT_NOESCAPE -# endif -#endif -#if !defined(SWIFT_RELEASES_ARGUMENT) -# if __has_attribute(ns_consumed) -# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) -# else -# define SWIFT_RELEASES_ARGUMENT -# endif -#endif -#if !defined(SWIFT_WARN_UNUSED_RESULT) -# if __has_attribute(warn_unused_result) -# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -# else -# define SWIFT_WARN_UNUSED_RESULT -# endif -#endif -#if !defined(SWIFT_NORETURN) -# if __has_attribute(noreturn) -# define SWIFT_NORETURN __attribute__((noreturn)) -# else -# define SWIFT_NORETURN -# endif -#endif -#if !defined(SWIFT_CLASS_EXTRA) -# define SWIFT_CLASS_EXTRA -#endif -#if !defined(SWIFT_PROTOCOL_EXTRA) -# define SWIFT_PROTOCOL_EXTRA -#endif -#if !defined(SWIFT_ENUM_EXTRA) -# define SWIFT_ENUM_EXTRA -#endif -#if !defined(SWIFT_CLASS) -# if __has_attribute(objc_subclassing_restricted) -# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA -# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA -# else -# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA -# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA -# endif -#endif -#if !defined(SWIFT_RESILIENT_CLASS) -# if __has_attribute(objc_class_stub) -# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) -# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) -# else -# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) -# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) -# endif -#endif -#if !defined(SWIFT_PROTOCOL) -# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA -# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA -#endif -#if !defined(SWIFT_EXTENSION) -# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) -#endif -#if !defined(OBJC_DESIGNATED_INITIALIZER) -# if __has_attribute(objc_designated_initializer) -# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -# else -# define OBJC_DESIGNATED_INITIALIZER -# endif -#endif -#if !defined(SWIFT_ENUM_ATTR) -# if __has_attribute(enum_extensibility) -# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) -# else -# define SWIFT_ENUM_ATTR(_extensibility) -# endif -#endif -#if !defined(SWIFT_ENUM) -# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type -# if __has_feature(generalized_swift_name) -# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type -# else -# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) -# endif -#endif -#if !defined(SWIFT_UNAVAILABLE) -# define SWIFT_UNAVAILABLE __attribute__((unavailable)) -#endif -#if !defined(SWIFT_UNAVAILABLE_MSG) -# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) -#endif -#if !defined(SWIFT_AVAILABILITY) -# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) -#endif -#if !defined(SWIFT_WEAK_IMPORT) -# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) -#endif -#if !defined(SWIFT_DEPRECATED) -# define SWIFT_DEPRECATED __attribute__((deprecated)) -#endif -#if !defined(SWIFT_DEPRECATED_MSG) -# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) -#endif -#if !defined(SWIFT_DEPRECATED_OBJC) -# if __has_feature(attribute_diagnose_if_objc) -# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) -# else -# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) -# endif -#endif -#if defined(__OBJC__) -#if !defined(IBSegueAction) -# define IBSegueAction -#endif -#endif -#if !defined(SWIFT_EXTERN) -# if defined(__cplusplus) -# define SWIFT_EXTERN extern "C" -# else -# define SWIFT_EXTERN extern -# endif -#endif -#if !defined(SWIFT_CALL) -# define SWIFT_CALL __attribute__((swiftcall)) -#endif -#if !defined(SWIFT_INDIRECT_RESULT) -# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) -#endif -#if !defined(SWIFT_CONTEXT) -# define SWIFT_CONTEXT __attribute__((swift_context)) -#endif -#if !defined(SWIFT_ERROR_RESULT) -# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) -#endif -#if defined(__cplusplus) -# define SWIFT_NOEXCEPT noexcept -#else -# define SWIFT_NOEXCEPT -#endif -#if defined(_WIN32) -#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) -# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) -#endif -#else -#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) -# define SWIFT_IMPORT_STDLIB_SYMBOL -#endif -#endif -#if defined(__OBJC__) -#if __has_feature(objc_modules) -#if __has_warning("-Watimport-in-framework-header") -#pragma clang diagnostic ignored "-Watimport-in-framework-header" -#endif -@import Dispatch; -@import Foundation; -@import ObjectiveC; -#endif - -#endif -#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" -#pragma clang diagnostic ignored "-Wduplicate-method-arg" -#if __has_warning("-Wpragma-clang-attribute") -# pragma clang diagnostic ignored "-Wpragma-clang-attribute" -#endif -#pragma clang diagnostic ignored "-Wunknown-pragmas" -#pragma clang diagnostic ignored "-Wnullability" -#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" - -#if __has_attribute(external_source_symbol) -# pragma push_macro("any") -# undef any -# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="MultiplatformBleAdapter",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) -# pragma pop_macro("any") -#endif - -#if defined(__OBJC__) -@protocol BleClientManagerDelegate; -@class NSString; -@class NSError; - -SWIFT_PROTOCOL("_TtP23MultiplatformBleAdapter10BleAdapter_") -@protocol BleAdapter -@property (nonatomic, strong) id _Nullable delegate; -- (nonnull instancetype)initWithQueue:(dispatch_queue_t _Nonnull)queue restoreIdentifierKey:(NSString * _Nullable)restoreIdentifierKey; -- (void)invalidate; -- (void)cancelTransaction:(NSString * _Nonnull)transactionId; -- (void)setLogLevel:(NSString * _Nonnull)logLevel; -- (void)logLevel:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)enable:(NSString * _Nonnull)transactionId resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)disable:(NSString * _Nonnull)transactionId resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)state:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)startDeviceScan:(NSArray * _Nullable)filteredUUIDs options:(NSDictionary * _Nullable)options; -- (void)stopDeviceScan; -- (void)readRSSIForDevice:(NSString * _Nonnull)deviceIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)requestMTUForDevice:(NSString * _Nonnull)deviceIdentifier mtu:(NSInteger)mtu transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)requestConnectionPriorityForDevice:(NSString * _Nonnull)deviceIdentifier connectionPriority:(NSInteger)connectionPriority transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)devices:(NSArray * _Nonnull)deviceIdentifiers resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)connectedDevices:(NSArray * _Nonnull)serviceUUIDs resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)connectToDevice:(NSString * _Nonnull)deviceIdentifier options:(NSDictionary * _Nullable)options resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)cancelDeviceConnection:(NSString * _Nonnull)deviceIdentifier resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)isDeviceConnected:(NSString * _Nonnull)deviceIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)discoverAllServicesAndCharacteristicsForDevice:(NSString * _Nonnull)deviceIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)servicesForDevice:(NSString * _Nonnull)deviceIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)characteristicsForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)characteristicsForService:(double)serviceIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)descriptorsForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)descriptorsForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)descriptorsForCharacteristic:(double)characteristicIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readCharacteristicForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readCharacteristicForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readCharacteristic:(double)characteristicIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeCharacteristicForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID valueBase64:(NSString * _Nonnull)valueBase64 response:(BOOL)response transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeCharacteristicForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID valueBase64:(NSString * _Nonnull)valueBase64 response:(BOOL)response transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeCharacteristic:(double)characteristicIdentifier valueBase64:(NSString * _Nonnull)valueBase64 response:(BOOL)response transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)monitorCharacteristicForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)monitorCharacteristicForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)monitorCharacteristic:(double)characteristicIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptorForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptorForService:(double)serviceId characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptorForCharacteristic:(double)characteristicID descriptorUUID:(NSString * _Nonnull)descriptorUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptor:(double)descriptorID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptorForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptorForService:(double)serviceID characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptorForCharacteristic:(double)characteristicID descriptorUUID:(NSString * _Nonnull)descriptorUUID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptor:(double)descriptorID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -@end - - -SWIFT_CLASS("_TtC23MultiplatformBleAdapter17BleAdapterFactory") -@interface BleAdapterFactory : NSObject -+ (id _Nonnull)getNewAdapterWithQueue:(dispatch_queue_t _Nonnull)queue restoreIdentifierKey:(NSString * _Nullable)restoreIdentifierKey SWIFT_WARN_UNUSED_RESULT; -+ (void)setBleAdapterCreator:(id _Nonnull (^ _Nonnull)(dispatch_queue_t _Nonnull, NSString * _Nullable))bleAdapterCreator; -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -@end - - -SWIFT_CLASS("_TtC23MultiplatformBleAdapter16BleClientManager") -@interface BleClientManager : NSObject -@property (nonatomic, strong) id _Nullable delegate; -- (nonnull instancetype)initWithQueue:(dispatch_queue_t _Nonnull)queue restoreIdentifierKey:(NSString * _Nullable)restoreIdentifierKey OBJC_DESIGNATED_INITIALIZER; -- (void)invalidate; -- (void)cancelTransaction:(NSString * _Nonnull)transactionId; -- (void)setLogLevel:(NSString * _Nonnull)logLevel; -- (void)logLevel:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)enable:(NSString * _Nonnull)transactionId resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)disable:(NSString * _Nonnull)transactionId resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)state:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)startDeviceScan:(NSArray * _Nullable)filteredUUIDs options:(NSDictionary * _Nullable)options; -- (void)stopDeviceScan; -- (void)readRSSIForDevice:(NSString * _Nonnull)deviceIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)requestMTUForDevice:(NSString * _Nonnull)deviceIdentifier mtu:(NSInteger)mtu transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)requestConnectionPriorityForDevice:(NSString * _Nonnull)deviceIdentifier connectionPriority:(NSInteger)connectionPriority transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)devices:(NSArray * _Nonnull)deviceIdentifiers resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)connectedDevices:(NSArray * _Nonnull)serviceUUIDs resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)connectToDevice:(NSString * _Nonnull)deviceIdentifier options:(NSDictionary * _Nullable)options resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)cancelDeviceConnection:(NSString * _Nonnull)deviceIdentifier resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)isDeviceConnected:(NSString * _Nonnull)deviceIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)discoverAllServicesAndCharacteristicsForDevice:(NSString * _Nonnull)deviceIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)servicesForDevice:(NSString * _Nonnull)deviceIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)characteristicsForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)characteristicsForService:(double)serviceIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)descriptorsForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)descriptorsForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)descriptorsForCharacteristic:(double)characteristicIdentifier resolve:(SWIFT_NOESCAPE void (^ _Nonnull)(id _Nullable))resolve reject:(SWIFT_NOESCAPE void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readCharacteristicForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readCharacteristicForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readCharacteristic:(double)characteristicIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeCharacteristicForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID valueBase64:(NSString * _Nonnull)valueBase64 response:(BOOL)response transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeCharacteristicForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID valueBase64:(NSString * _Nonnull)valueBase64 response:(BOOL)response transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeCharacteristic:(double)characteristicIdentifier valueBase64:(NSString * _Nonnull)valueBase64 response:(BOOL)response transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)monitorCharacteristicForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)monitorCharacteristicForService:(double)serviceIdentifier characteristicUUID:(NSString * _Nonnull)characteristicUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)monitorCharacteristic:(double)characteristicIdentifier transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptorForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptorForService:(double)serviceId characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptorForCharacteristic:(double)characteristicID descriptorUUID:(NSString * _Nonnull)descriptorUUID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)readDescriptor:(double)descriptorID transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptorForDevice:(NSString * _Nonnull)deviceIdentifier serviceUUID:(NSString * _Nonnull)serviceUUID characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptorForService:(double)serviceID characteristicUUID:(NSString * _Nonnull)characteristicUUID descriptorUUID:(NSString * _Nonnull)descriptorUUID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptorForCharacteristic:(double)characteristicID descriptorUUID:(NSString * _Nonnull)descriptorUUID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (void)writeDescriptor:(double)descriptorID valueBase64:(NSString * _Nonnull)valueBase64 transactionId:(NSString * _Nonnull)transactionId resolve:(void (^ _Nonnull)(id _Nullable))resolve reject:(void (^ _Nonnull)(NSString * _Nullable, NSString * _Nullable, NSError * _Nullable))reject; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -@interface BleClientManager (SWIFT_EXTENSION(MultiplatformBleAdapter)) -@end - - -SWIFT_PROTOCOL("_TtP23MultiplatformBleAdapter24BleClientManagerDelegate_") -@protocol BleClientManagerDelegate -- (void)dispatchEvent:(NSString * _Nonnull)name value:(id _Nonnull)value; -@end - - -SWIFT_CLASS("_TtC23MultiplatformBleAdapter8BleEvent") -@interface BleEvent : NSObject -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull scanEvent;) -+ (NSString * _Nonnull)scanEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull readEvent;) -+ (NSString * _Nonnull)readEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull stateChangeEvent;) -+ (NSString * _Nonnull)stateChangeEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull restoreStateEvent;) -+ (NSString * _Nonnull)restoreStateEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull disconnectionEvent;) -+ (NSString * _Nonnull)disconnectionEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull connectingEvent;) -+ (NSString * _Nonnull)connectingEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull connectedEvent;) -+ (NSString * _Nonnull)connectedEvent SWIFT_WARN_UNUSED_RESULT; -SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSArray * _Nonnull events;) -+ (NSArray * _Nonnull)events SWIFT_WARN_UNUSED_RESULT; -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -@end - - - - - - - - - - - - - -#endif -#if defined(__cplusplus) -#endif -#if __has_attribute(external_source_symbol) -# pragma clang attribute pop -#endif -#pragma clang diagnostic pop -#endif diff --git a/ios/BlePlx.h b/ios/BlePlx.h deleted file mode 100644 index 9127ca965..000000000 --- a/ios/BlePlx.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// BleClient.h -// BleClient -// -// Created by Przemysław Lenart on 27/07/16. -// Copyright © 2016 Polidea. All rights reserved. -// - -#import -#import -#import - -@interface BlePlx : RCTEventEmitter - -@end diff --git a/ios/BlePlx.m b/ios/BlePlx.m deleted file mode 100644 index 8e0da36bc..000000000 --- a/ios/BlePlx.m +++ /dev/null @@ -1,510 +0,0 @@ -// -// BleClient.m -// BleClient -// -// Created by Przemysław Lenart on 27/07/16. -// Copyright © 2016 Polidea. All rights reserved. -// - -#import "BlePlx.h" -#import "BlePlx-Swift.h" - - -@interface BlePlx () -@property(nonatomic) BleClientManager* manager; -@end - -@implementation BlePlx -{ - bool hasListeners; -} - -@synthesize methodQueue = _methodQueue; - -RCT_EXPORT_MODULE(); - - -- (void)dispatchEvent:(NSString * _Nonnull)name value:(id _Nonnull)value { - if (hasListeners) { - [self sendEventWithName:name body:value]; - } -} - -- (void)startObserving { - hasListeners = YES; -} - -- (void)stopObserving { - hasListeners = NO; -} - -- (NSArray *)supportedEvents { - return BleEvent.events; -} - -- (NSDictionary *)constantsToExport { - NSMutableDictionary* consts = [NSMutableDictionary new]; - for (NSString* event in BleEvent.events) { - [consts setValue:event forKey:event]; - } - return consts; -} - -+ (BOOL)requiresMainQueueSetup { - return YES; -} - -RCT_EXPORT_METHOD(createClient:(NSString*)restoreIdentifierKey) { - _manager = [BleAdapterFactory getNewAdapterWithQueue:self.methodQueue - restoreIdentifierKey:restoreIdentifierKey]; - _manager.delegate = self; -} - -RCT_EXPORT_METHOD(destroyClient) { - [_manager invalidate]; - _manager = nil; -} - -- (void)invalidate { - [self destroyClient]; -} - -// Mark: Monitoring state ---------------------------------------------------------------------------------------------- - -RCT_EXPORT_METHOD( enable:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager enable:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD( disable:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager disable:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD( state:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager state:resolve - reject:reject]; -} - -// Mark: Scanning ------------------------------------------------------------------------------------------------------ - -RCT_EXPORT_METHOD(startDeviceScan:(NSArray*)filteredUUIDs - options:(NSDictionary*)options) { - [_manager startDeviceScan:filteredUUIDs options:options]; -} - -RCT_EXPORT_METHOD(stopDeviceScan) { - [_manager stopDeviceScan]; -} - -RCT_EXPORT_METHOD(requestConnectionPriorityForDevice:(NSString*)deviceIdentifier - connectionPriority:(NSInteger)connectionPriority - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager requestConnectionPriorityForDevice:deviceIdentifier - connectionPriority:connectionPriority - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(readRSSIForDevice:(NSString*)deviceIdentifier - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readRSSIForDevice:deviceIdentifier - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(requestMTUForDevice:(NSString*)deviceIdentifier - mtu:(NSInteger)mtu - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager requestMTUForDevice:deviceIdentifier - mtu:mtu - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -// Mark: Device management --------------------------------------------------------------------------------------------- - -RCT_EXPORT_METHOD(devices:(NSArray*)deviceIdentifiers - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager devices:deviceIdentifiers - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(connectedDevices:(NSArray*)serviceUUIDs - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager connectedDevices:serviceUUIDs - resolve:resolve - reject:reject]; -} - -// Mark: Connection management ----------------------------------------------------------------------------------------- - -RCT_EXPORT_METHOD(connectToDevice:(NSString*)deviceIdentifier - options:(NSDictionary*)options - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager connectToDevice:deviceIdentifier - options:options - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(cancelDeviceConnection:(NSString*)deviceIdentifier - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager cancelDeviceConnection:deviceIdentifier - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(isDeviceConnected:(NSString*)deviceIdentifier - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager isDeviceConnected:deviceIdentifier - resolve:resolve - reject:reject]; -} - -// Mark: Discovery ----------------------------------------------------------------------------------------------------- - -RCT_EXPORT_METHOD(discoverAllServicesAndCharacteristicsForDevice:(NSString*)deviceIdentifier - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager discoverAllServicesAndCharacteristicsForDevice:deviceIdentifier - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -// Mark: Service and characteristic getters ---------------------------------------------------------------------------- - -RCT_EXPORT_METHOD(servicesForDevice:(NSString*)deviceIdentifier - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager servicesForDevice:deviceIdentifier - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(characteristicsForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager characteristicsForDevice:deviceIdentifier - serviceUUID:serviceUUID - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(characteristicsForService:(nonnull NSNumber*)serviceIdentifier - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager characteristicsForService:serviceIdentifier.doubleValue - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(descriptorsForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - characteristicUUID:(NSString*)characteristicUUID - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager descriptorsForDevice:deviceIdentifier - serviceUUID:serviceUUID - characteristicUUID:characteristicUUID - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(descriptorsForService:(nonnull NSNumber*)serviceIdentifier - characteristicUUID:(NSString*)characteristicUUID - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager descriptorsForService:serviceIdentifier.doubleValue - characteristicUUID:characteristicUUID - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(descriptorsForCharacteristic:(nonnull NSNumber*)characteristicIdentifier - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager descriptorsForCharacteristic:characteristicIdentifier.doubleValue - resolve:resolve - reject:reject]; -} - -// Mark: Characteristics operations ------------------------------------------------------------------------------------ - -RCT_EXPORT_METHOD(readCharacteristicForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - characteristicUUID:(NSString*)characteristicUUID - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readCharacteristicForDevice:deviceIdentifier - serviceUUID:serviceUUID - characteristicUUID:characteristicUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(readCharacteristicForService:(nonnull NSNumber*)serviceIdentifier - characteristicUUID:(NSString*)characteristicUUID - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readCharacteristicForService:serviceIdentifier.doubleValue - characteristicUUID:characteristicUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(readCharacteristic:(nonnull NSNumber*)characteristicIdentifier - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readCharacteristic:characteristicIdentifier.doubleValue - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeCharacteristicForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - characteristicUUID:(NSString*)characteristicUUID - valueBase64:(NSString*)valueBase64 - withResponse:(BOOL)response - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeCharacteristicForDevice:deviceIdentifier - serviceUUID:serviceUUID - characteristicUUID:characteristicUUID - valueBase64:valueBase64 - response:response - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeCharacteristicForService:(nonnull NSNumber*)serviceIdentifier - characteristicUUID:(NSString*)characteristicUUID - valueBase64:(NSString*)valueBase64 - withResponse:(BOOL)response - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeCharacteristicForService:serviceIdentifier.doubleValue - characteristicUUID:characteristicUUID - valueBase64:valueBase64 - response:response - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeCharacteristic:(nonnull NSNumber*)characteristicIdentifier - valueBase64:(NSString*)valueBase64 - withResponse:(BOOL)response - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeCharacteristic:characteristicIdentifier.doubleValue - valueBase64:valueBase64 - response:response - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(monitorCharacteristicForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - characteristicUUID:(NSString*)characteristicUUID - transactionID:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager monitorCharacteristicForDevice:deviceIdentifier - serviceUUID:serviceUUID - characteristicUUID:characteristicUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(monitorCharacteristicForService:(nonnull NSNumber*)serviceIdentifier - characteristicUUID:(NSString*)characteristicUUID - transactionID:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager monitorCharacteristicForService:serviceIdentifier.doubleValue - characteristicUUID:characteristicUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(monitorCharacteristic:(nonnull NSNumber*)characteristicIdentifier - transactionID:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager monitorCharacteristic:characteristicIdentifier.doubleValue - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -// Mark: Characteristics operations ------------------------------------------------------------------------------------ - -RCT_EXPORT_METHOD(readDescriptorForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - characteristicUUID:(NSString*)characteristicUUID - descriptorUUID:(NSString*)descriptorUUID - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readDescriptorForDevice:deviceIdentifier - serviceUUID:serviceUUID - characteristicUUID:characteristicUUID - descriptorUUID:descriptorUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(readDescriptorForService:(nonnull NSNumber*)serviceIdentifier - characteristicUUID:(NSString*)characteristicUUID - descriptorUUID:(NSString*)descriptorUUID - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readDescriptorForService:serviceIdentifier.doubleValue - characteristicUUID:characteristicUUID - descriptorUUID:descriptorUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - - -RCT_EXPORT_METHOD(readDescriptorForCharacteristic:(nonnull NSNumber*)characteristicIdentifier - descriptorUUID:(NSString*)descriptorUUID - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readDescriptorForCharacteristic:characteristicIdentifier.doubleValue - descriptorUUID:descriptorUUID - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(readDescriptor:(nonnull NSNumber*)descriptorIdentifier - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager readDescriptor:descriptorIdentifier.doubleValue - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeDescriptorForDevice:(NSString*)deviceIdentifier - serviceUUID:(NSString*)serviceUUID - characteristicUUID:(NSString*)characteristicUUID - descriptorUUID:(NSString*)descriptorUUID - valueBase64:(NSString*)valueBase64 - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeDescriptorForDevice:deviceIdentifier - serviceUUID:serviceUUID - characteristicUUID:characteristicUUID - descriptorUUID:descriptorUUID - valueBase64:valueBase64 - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeDescriptorForService:(nonnull NSNumber*)serviceIdentifier - characteristicUUID:(NSString*)characteristicUUID - descriptorUUID:(NSString*)descriptorUUID - valueBase64:(NSString*)valueBase64 - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeDescriptorForService:serviceIdentifier.doubleValue - characteristicUUID:characteristicUUID - descriptorUUID:descriptorUUID - valueBase64:valueBase64 - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeDescriptorForCharacteristic:(nonnull NSNumber*)characteristicIdentifier - descriptorUUID:(NSString*)descriptorUUID - valueBase64:(NSString*)valueBase64 - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeDescriptorForCharacteristic:characteristicIdentifier.doubleValue - descriptorUUID:descriptorUUID - valueBase64:valueBase64 - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -RCT_EXPORT_METHOD(writeDescriptor:(nonnull NSNumber*)descriptorIdentifier - valueBase64:(NSString*)valueBase64 - transactionId:(NSString*)transactionId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager writeDescriptor:descriptorIdentifier.doubleValue - valueBase64:valueBase64 - transactionId:transactionId - resolve:resolve - reject:reject]; -} - -// Mark: Other operations ---------------------------------------------------------------------------------------------- - -RCT_EXPORT_METHOD(cancelTransaction:(NSString*)transactionId) { - [_manager cancelTransaction:transactionId]; -} - -RCT_EXPORT_METHOD(setLogLevel:(NSString*)logLevel) { - [_manager setLogLevel:logLevel]; -} - -RCT_EXPORT_METHOD(logLevel:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [_manager logLevel:resolve - reject:reject]; -} - -@end diff --git a/ios/BlePlx.mm b/ios/BlePlx.mm new file mode 100644 index 000000000..e831f0c4c --- /dev/null +++ b/ios/BlePlx.mm @@ -0,0 +1,259 @@ +// +// BlePlx.mm +// react-native-ble-plx +// +// TurboModule entry point — ObjC++ bridge to Swift implementation +// + +#import +#import +#import + +// Auto-generated Swift bridge header +#if __has_include("react_native_ble_plx-Swift.h") +#import "react_native_ble_plx-Swift.h" +#else +#import +#endif + +@interface BlePlx : NativeBlePlxSpecBase +@end + +@implementation BlePlx { + BLEModuleImpl *_impl; +} + +RCT_EXPORT_MODULE(NativeBlePlx) + +- (instancetype)init { + self = [super init]; + if (self) { + _impl = [[BLEModuleImpl alloc] initWithEventEmitter:self]; + } + return self; +} + ++ (BOOL)requiresMainQueueSetup { + return NO; +} + +- (void)dealloc { + [_impl invalidate]; +} + +// MARK: - Lifecycle + +- (void)createClient:(NSString * _Nullable)restoreStateIdentifier + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl createClientWithRestoreStateIdentifier:restoreStateIdentifier resolve:resolve reject:reject]; +} + +- (void)destroyClient:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl destroyClientWithResolve:resolve reject:reject]; +} + +// MARK: - State + +- (void)state:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl stateWithResolve:resolve reject:reject]; +} + +// MARK: - Scanning + +- (void)startDeviceScan:(NSArray * _Nullable)uuids + options:(JS::NativeBlePlx::SpecStartDeviceScanOptions &)options { + NSMutableDictionary *optionsDict = [NSMutableDictionary dictionary]; + { + if (options.scanMode().has_value()) { + optionsDict[@"scanMode"] = @(options.scanMode().value()); + } + if (options.callbackType().has_value()) { + optionsDict[@"callbackType"] = @(options.callbackType().value()); + } + if (options.legacyScan().has_value()) { + optionsDict[@"legacyScan"] = @(options.legacyScan().value()); + } + if (options.allowDuplicates().has_value()) { + optionsDict[@"allowDuplicates"] = @(options.allowDuplicates().value()); + } + } + [_impl startDeviceScanWithUuids:uuids options:optionsDict]; +} + +- (void)stopDeviceScan:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl stopDeviceScanWithResolve:resolve reject:reject]; +} + +// MARK: - Connection + +- (void)connectToDevice:(NSString *)deviceId + options:(JS::NativeBlePlx::SpecConnectToDeviceOptions &)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + NSMutableDictionary *optionsDict = [NSMutableDictionary dictionary]; + if (options.autoConnect().has_value()) { + optionsDict[@"autoConnect"] = @(options.autoConnect().value()); + } + if (options.timeout().has_value()) { + optionsDict[@"timeout"] = @(options.timeout().value()); + } + if (options.retries().has_value()) { + optionsDict[@"retries"] = @(options.retries().value()); + } + if (options.retryDelay().has_value()) { + optionsDict[@"retryDelay"] = @(options.retryDelay().value()); + } + if (options.requestMtu().has_value()) { + optionsDict[@"requestMtu"] = @(options.requestMtu().value()); + } + [_impl connectToDeviceWithDeviceId:deviceId options:optionsDict resolve:resolve reject:reject]; +} + +- (void)cancelDeviceConnection:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl cancelDeviceConnectionWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +- (void)isDeviceConnected:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl isDeviceConnectedWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +// MARK: - Discovery + +- (void)discoverAllServicesAndCharacteristics:(NSString *)deviceId + transactionId:(NSString * _Nullable)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl discoverAllServicesAndCharacteristicsWithDeviceId:deviceId transactionId:transactionId resolve:resolve reject:reject]; +} + +// MARK: - Read/Write + +- (void)readCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + transactionId:(NSString * _Nullable)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl readCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid transactionId:transactionId resolve:resolve reject:reject]; +} + +- (void)writeCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + value:(NSString *)value + withResponse:(BOOL)withResponse + transactionId:(NSString * _Nullable)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl writeCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid value:value withResponse:withResponse transactionId:transactionId resolve:resolve reject:reject]; +} + +// MARK: - Monitor + +- (void)monitorCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + subscriptionType:(NSString * _Nullable)subscriptionType + transactionId:(NSString * _Nullable)transactionId { + [_impl monitorCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid subscriptionType:subscriptionType transactionId:transactionId]; +} + +// MARK: - MTU + +- (void)getMtu:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl getMtuWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +- (void)requestMtu:(NSString *)deviceId + mtu:(double)mtu + transactionId:(NSString * _Nullable)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl requestMtuWithDeviceId:deviceId mtu:(NSInteger)mtu transactionId:transactionId resolve:resolve reject:reject]; +} + +// MARK: - PHY + +- (void)requestPhy:(NSString *)deviceId + txPhy:(double)txPhy + rxPhy:(double)rxPhy + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl requestPhyWithDeviceId:deviceId txPhy:(NSInteger)txPhy rxPhy:(NSInteger)rxPhy resolve:resolve reject:reject]; +} + +- (void)readPhy:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl readPhyWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +// MARK: - Connection Priority + +- (void)requestConnectionPriority:(NSString *)deviceId + priority:(double)priority + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl requestConnectionPriorityWithDeviceId:deviceId priority:(NSInteger)priority resolve:resolve reject:reject]; +} + +// MARK: - L2CAP + +- (void)openL2CAPChannel:(NSString *)deviceId + psm:(double)psm + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl openL2CAPChannelWithDeviceId:deviceId psm:(NSInteger)psm resolve:resolve reject:reject]; +} + +- (void)writeL2CAPChannel:(double)channelId + data:(NSString *)data + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl writeL2CAPChannelWithChannelId:(NSInteger)channelId data:data resolve:resolve reject:reject]; +} + +- (void)closeL2CAPChannel:(double)channelId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl closeL2CAPChannelWithChannelId:(NSInteger)channelId resolve:resolve reject:reject]; +} + +// MARK: - Bonding + +- (void)getBondedDevices:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl getBondedDevicesWithResolve:resolve reject:reject]; +} + +// MARK: - Authorization + +- (void)getAuthorizationStatus:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl getAuthorizationStatusWithResolve:resolve reject:reject]; +} + +// MARK: - Cancellation + +- (void)cancelTransaction:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl cancelTransactionWithTransactionId:transactionId resolve:resolve reject:reject]; +} + +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} + +@end diff --git a/ios/CentralManagerDelegate.swift b/ios/CentralManagerDelegate.swift new file mode 100644 index 000000000..f950fc31c --- /dev/null +++ b/ios/CentralManagerDelegate.swift @@ -0,0 +1,241 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - Connection result + +struct ConnectionResult: Sendable { + let peripheralId: UUID + let error: Error? +} + +// MARK: - CentralManagerDelegate + +/// Bridges CBCentralManagerDelegate callbacks to async/await patterns. +/// Uses AsyncStream for scan results and CheckedContinuation for connections. +final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate, Sendable { + + // MARK: - State + + private let stateSubject = AsyncStreamBridge() + private let scanSubject = AsyncStreamBridge() + private let connectionSubject = AsyncStreamBridge() + private let disconnectionSubject = AsyncStreamBridge() + private let restorationSubject = AsyncStreamBridge() + + /// Pending connection continuations keyed by peripheral UUID + private let pendingConnections = LockedDictionary>() + + /// Raw restoration dicts buffered for same-queue access by BLEActor/StateRestoration. + /// Only accessed from the CB queue — not Sendable, not crossed across isolation domains. + private let rawRestorationDicts = LockedBox<[[String: Any]]>([]) + + /// Discovered peripherals retained during scanning so CoreBluetooth doesn't deallocate them. + private let discoveredPeripherals = LockedDictionary() + + // MARK: - Streams + + var stateStream: AsyncStream { stateSubject.stream } + var scanStream: AsyncStream { scanSubject.stream } + var disconnectionStream: AsyncStream { disconnectionSubject.stream } + var restorationStream: AsyncStream { restorationSubject.stream } + + // MARK: - Connection management + + func addConnectionContinuation(_ continuation: CheckedContinuation, for peripheralId: UUID) { + pendingConnections.set(peripheralId, value: continuation) + } + + func removeConnectionContinuation(for peripheralId: UUID) { + pendingConnections.removeValue(forKey: peripheralId) + } + + // MARK: - CBCentralManagerDelegate + + func centralManagerDidUpdateState(_ central: CBCentralManager) { + stateSubject.yield(central.state) + } + + func centralManager( + _ central: CBCentralManager, + didDiscover peripheral: CBPeripheral, + advertisementData: [String: Any], + rssi RSSI: NSNumber + ) { + // Retain the peripheral so CoreBluetooth doesn't deallocate it before connection + discoveredPeripherals.set(peripheral.identifier, value: peripheral) + + let snapshot = EventSerializer.scanResult( + from: peripheral, + advertisementData: advertisementData, + rssi: RSSI + ) + scanSubject.yield(snapshot) + } + + /// Retrieve a discovered peripheral by UUID. Returns nil if not found. + func discoveredPeripheral(for uuid: UUID) -> CBPeripheral? { + return discoveredPeripherals.value(forKey: uuid) + } + + /// Clear discovered peripherals (e.g., when scan stops or manager is destroyed). + func clearDiscoveredPeripherals() { + discoveredPeripherals.removeAllValues() + } + + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + if let continuation = pendingConnections.removeValue(forKey: peripheral.identifier) { + continuation.resume() + } + connectionSubject.yield(ConnectionResult(peripheralId: peripheral.identifier, error: nil)) + } + + func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + let bleError = error.map { + ErrorConverter.from(cbError: $0, deviceId: peripheral.identifier.uuidString, operation: "connect") + } ?? BleError( + code: .deviceConnectionFailed, + message: "Failed to connect to device", + deviceId: peripheral.identifier.uuidString + ) + + if let continuation = pendingConnections.removeValue(forKey: peripheral.identifier) { + continuation.resume(throwing: bleError) + } + } + + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + // If there's a pending connection continuation, resume it with an error + if let continuation = pendingConnections.removeValue(forKey: peripheral.identifier) { + let bleError = BleError( + code: .deviceDisconnected, + message: "Device disconnected during connection", + deviceId: peripheral.identifier.uuidString + ) + continuation.resume(throwing: bleError) + } + + disconnectionSubject.yield(ConnectionResult(peripheralId: peripheral.identifier, error: error)) + } + + func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) { + // willRestoreState fires BEFORE didUpdateState. + // Store the raw dict for same-queue access (peripherals are not Sendable). + var current = rawRestorationDicts.value + current.append(dict) + rawRestorationDicts.value = current + // Send the Sendable metadata through the stream for cross-actor signaling. + restorationSubject.yield(RestorationState(from: dict)) + } + + /// Consume buffered raw restoration dicts. Must be called from the CB queue. + func consumeRawRestorationDicts() -> [[String: Any]] { + let dicts = rawRestorationDicts.value + rawRestorationDicts.value = [] + return dicts + } + + @available(iOS 13.0, *) + func centralManager(_ central: CBCentralManager, connectionEventDidOccur event: CBConnectionEvent, for peripheral: CBPeripheral) { + // Forward as a disconnection if peer disconnected + if event == .peerDisconnected { + disconnectionSubject.yield(ConnectionResult(peripheralId: peripheral.identifier, error: nil)) + } + } + + // MARK: - Cleanup + + func finish() { + stateSubject.finish() + scanSubject.finish() + connectionSubject.finish() + disconnectionSubject.finish() + restorationSubject.finish() + + // Cancel all pending connections + pendingConnections.removeAll { continuation in + continuation.resume(throwing: BleError( + code: .bluetoothManagerDestroyed, + message: "BLE manager was destroyed" + )) + } + } +} + +// MARK: - Thread-safe AsyncStream bridge + +/// A simple Sendable wrapper around AsyncStream continuation +final class AsyncStreamBridge: Sendable { + private let _continuation: LockedBox.Continuation?> + let stream: AsyncStream + + init() { + let box = LockedBox.Continuation?>(nil) + self._continuation = box + self.stream = AsyncStream { continuation in + box.value = continuation + } + } + + func yield(_ value: T) { + _continuation.value?.yield(value) + } + + func finish() { + _continuation.value?.finish() + _continuation.value = nil + } +} + +// MARK: - Thread-safe helpers + +/// A locked box for Sendable compliance +final class LockedBox: @unchecked Sendable { + private let lock = NSLock() + private var _value: T + + init(_ value: T) { + self._value = value + } + + var value: T { + get { lock.withLock { _value } } + set { lock.withLock { _value = newValue } } + } + + /// Atomically read and transform the value in a single locked operation. + @discardableResult + func mutate(_ transform: (inout T) -> R) -> R { + lock.withLock { transform(&_value) } + } +} + +/// Thread-safe dictionary +final class LockedDictionary: @unchecked Sendable { + private let lock = NSLock() + private var dict: [Key: Value] = [:] + + func set(_ key: Key, value: Value) { + lock.withLock { dict[key] = value } + } + + func removeValue(forKey key: Key) -> Value? { + lock.withLock { dict.removeValue(forKey: key) } + } + + func value(forKey key: Key) -> Value? { + lock.withLock { dict[key] } + } + + func removeAll(handler: (Value) -> Void) { + lock.withLock { + for (_, value) in dict { + handler(value) + } + dict.removeAll() + } + } + + func removeAllValues() { + lock.withLock { dict.removeAll() } + } +} diff --git a/ios/ErrorConverter.swift b/ios/ErrorConverter.swift new file mode 100644 index 000000000..c46a9ca97 --- /dev/null +++ b/ios/ErrorConverter.swift @@ -0,0 +1,296 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - BleErrorCode + +/// Unified error codes matching the JS-side BleErrorInfo type +enum BleErrorCode: Int, Sendable { + // General + case unknown = 0 + case bluetoothManagerDestroyed = 1 + case operationCancelled = 2 + case operationTimedOut = 3 + case operationStartFailed = 4 + case invalidIdentifiers = 5 + + // Bluetooth state + case bluetoothUnsupported = 100 + case bluetoothUnauthorized = 101 + case bluetoothPoweredOff = 102 + case bluetoothInUnknownState = 103 + case bluetoothResetting = 104 + + // Device + case deviceConnectionFailed = 200 + case deviceDisconnected = 201 + case deviceRSSIReadFailed = 202 + case deviceAlreadyConnected = 203 + case deviceNotFound = 204 + case deviceNotConnected = 205 + case deviceMTUChangeFailed = 206 + case deviceBondLost = 207 + + // Service/Characteristic + case servicesDiscoveryFailed = 300 + case includedServicesDiscoveryFailed = 301 + case characteristicsDiscoveryFailed = 302 + case descriptorsDiscoveryFailed = 303 + case servicesNotDiscovered = 304 + case characteristicsNotDiscovered = 305 + case descriptorsNotDiscovered = 306 + + // Characteristic operations + case characteristicReadFailed = 400 + case characteristicWriteFailed = 401 + case characteristicNotifyChangeFailed = 402 + case descriptorReadFailed = 403 + case descriptorWriteFailed = 404 + + // L2CAP + case l2capOpenFailed = 500 + case l2capWriteFailed = 501 + case l2capCloseFailed = 502 + case l2capNotConnected = 503 +} + +// MARK: - BleError + +struct BleError: Error, Sendable { + let code: BleErrorCode + let message: String + let isRetryable: Bool + let deviceId: String? + let serviceUuid: String? + let characteristicUuid: String? + let operation: String? + let nativeDomain: String? + let nativeCode: Int? + let attErrorCode: Int? + + init( + code: BleErrorCode, + message: String, + isRetryable: Bool = false, + deviceId: String? = nil, + serviceUuid: String? = nil, + characteristicUuid: String? = nil, + operation: String? = nil, + nativeDomain: String? = nil, + nativeCode: Int? = nil, + attErrorCode: Int? = nil + ) { + self.code = code + self.message = message + self.isRetryable = isRetryable + self.deviceId = deviceId + self.serviceUuid = serviceUuid + self.characteristicUuid = characteristicUuid + self.operation = operation + self.nativeDomain = nativeDomain + self.nativeCode = nativeCode + self.attErrorCode = attErrorCode + } + + /// Convert to dictionary matching JS BleErrorInfo type + func toDictionary() -> [String: Any] { + var dict: [String: Any] = [ + "code": code.rawValue, + "message": message, + "isRetryable": isRetryable, + "platform": "ios" + ] + dict["deviceId"] = deviceId + dict["serviceUuid"] = serviceUuid + dict["characteristicUuid"] = characteristicUuid + dict["operation"] = operation + dict["nativeDomain"] = nativeDomain + dict["nativeCode"] = nativeCode + dict["gattStatus"] = nil as Int? + dict["attErrorCode"] = attErrorCode + return dict + } +} + +// MARK: - ErrorConverter + +enum ErrorConverter { + + /// Convert a CBError to a BleError + static func from( + cbError: Error, + deviceId: String? = nil, + serviceUuid: String? = nil, + characteristicUuid: String? = nil, + operation: String? = nil + ) -> BleError { + let nsError = cbError as NSError + + // Check for CBATTError first + if nsError.domain == CBATTErrorDomain { + return fromATTError( + nsError: nsError, + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + operation: operation + ) + } + + // CBError domain + if nsError.domain == CBErrorDomain { + return fromCBError( + nsError: nsError, + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + operation: operation + ) + } + + // Generic error + return BleError( + code: .unknown, + message: cbError.localizedDescription, + isRetryable: false, + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + operation: operation, + nativeDomain: nsError.domain, + nativeCode: nsError.code + ) + } + + private static func fromCBError( + nsError: NSError, + deviceId: String?, + serviceUuid: String?, + characteristicUuid: String?, + operation: String? + ) -> BleError { + let cbCode = CBError.Code(rawValue: nsError.code) + let isRetryable = isRetryableCBError(cbCode) + + let bleCode: BleErrorCode + let message: String + + switch cbCode { + case .unknown: + bleCode = .unknown + message = "Unknown CoreBluetooth error" + case .peerRemovedPairingInformation: + bleCode = .deviceBondLost + message = "Peer removed pairing information" + case .connectionFailed: + bleCode = .deviceConnectionFailed + message = "Connection failed" + case .notConnected: + bleCode = .deviceNotConnected + message = "Device is not connected" + case .connectionLimitReached: + bleCode = .deviceConnectionFailed + message = "Connection limit reached" + return BleError(code: bleCode, message: message, isRetryable: true, + deviceId: deviceId, serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, operation: operation, + nativeDomain: nsError.domain, nativeCode: nsError.code) + case .operationNotSupported: + bleCode = .operationStartFailed + message = "Operation not supported" + default: + bleCode = .unknown + message = nsError.localizedDescription + } + + return BleError( + code: bleCode, + message: message, + isRetryable: isRetryable, + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + operation: operation, + nativeDomain: nsError.domain, + nativeCode: nsError.code + ) + } + + private static func fromATTError( + nsError: NSError, + deviceId: String?, + serviceUuid: String?, + characteristicUuid: String?, + operation: String? + ) -> BleError { + let attCode = nsError.code + + let bleCode: BleErrorCode + switch operation { + case "read": + bleCode = .characteristicReadFailed + case "write": + bleCode = .characteristicWriteFailed + case "notify": + bleCode = .characteristicNotifyChangeFailed + case "readDescriptor": + bleCode = .descriptorReadFailed + case "writeDescriptor": + bleCode = .descriptorWriteFailed + default: + bleCode = .unknown + } + + return BleError( + code: bleCode, + message: "ATT error: \(nsError.localizedDescription)", + isRetryable: isRetryableATTError(attCode), + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid, + operation: operation, + nativeDomain: nsError.domain, + nativeCode: nsError.code, + attErrorCode: attCode + ) + } + + private static func isRetryableCBError(_ code: CBError.Code?) -> Bool { + guard let code = code else { return false } + switch code { + case .connectionFailed, .connectionLimitReached: + return true + default: + return false + } + } + + private static func isRetryableATTError(_ code: Int) -> Bool { + // ATT errors that may succeed on retry + switch CBATTError.Code(rawValue: code) { + case .insufficientResources, .unlikelyError: + return true + default: + return false + } + } + + /// Check if the current Bluetooth state allows operations + static func bleStateError(for state: CBManagerState) -> BleError? { + switch state { + case .poweredOn: + return nil + case .poweredOff: + return BleError(code: .bluetoothPoweredOff, message: "Bluetooth is powered off") + case .unauthorized: + return BleError(code: .bluetoothUnauthorized, message: "Bluetooth is unauthorized") + case .unsupported: + return BleError(code: .bluetoothUnsupported, message: "Bluetooth is unsupported on this device") + case .resetting: + return BleError(code: .bluetoothResetting, message: "Bluetooth is resetting", isRetryable: true) + case .unknown: + return BleError(code: .bluetoothInUnknownState, message: "Bluetooth state is unknown") + @unknown default: + return BleError(code: .bluetoothInUnknownState, message: "Bluetooth state is unknown") + } + } +} diff --git a/ios/EventSerializer.swift b/ios/EventSerializer.swift new file mode 100644 index 000000000..38bf3a6dc --- /dev/null +++ b/ios/EventSerializer.swift @@ -0,0 +1,182 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - Sendable value types extracted from CB objects + +/// Sendable snapshot of a peripheral's state +struct PeripheralSnapshot: Sendable { + let id: String + let name: String? + let rssi: Int + let mtu: Int + let isConnectable: Bool? + let serviceUuids: [String] + let manufacturerData: String? // Base64 + + func toDictionary() -> [String: Any] { + var dict: [String: Any] = [ + "id": id, + "name": name as Any, + "rssi": rssi, + "mtu": mtu, + "isConnectable": isConnectable as Any, + "serviceUuids": serviceUuids, + "manufacturerData": manufacturerData as Any + ] + return dict + } +} + +/// Sendable snapshot of a characteristic +struct CharacteristicSnapshot: Sendable { + let deviceId: String + let serviceUuid: String + let uuid: String + let value: String? // Base64 + let isNotifying: Bool + let isIndicatable: Bool + let isReadable: Bool + let isWritableWithResponse: Bool + let isWritableWithoutResponse: Bool + + func toDictionary() -> [String: Any] { + return [ + "deviceId": deviceId, + "serviceUuid": serviceUuid, + "uuid": uuid, + "value": value as Any, + "isNotifying": isNotifying, + "isIndicatable": isIndicatable, + "isReadable": isReadable, + "isWritableWithResponse": isWritableWithResponse, + "isWritableWithoutResponse": isWritableWithoutResponse + ] + } +} + +/// Sendable scan result +struct ScanResultSnapshot: Sendable { + let id: String + let name: String? + let rssi: Int + let serviceUuids: [String] + let manufacturerData: String? // Base64 + + func toDictionary() -> [String: Any] { + return [ + "id": id, + "name": name as Any, + "rssi": rssi, + "serviceUuids": serviceUuids, + "manufacturerData": manufacturerData as Any + ] + } +} + +// MARK: - EventSerializer + +/// Extracts Sendable values from CoreBluetooth objects. +/// NEVER pass CBPeripheral or CBCharacteristic across actor boundaries. +enum EventSerializer { + + /// Create a PeripheralSnapshot from a CBPeripheral. + /// Must be called on the CB queue (same actor context as the peripheral). + static func snapshot( + from peripheral: CBPeripheral, + rssi: Int = 0, + advertisementData: [String: Any]? = nil + ) -> PeripheralSnapshot { + let name = peripheral.name + let id = peripheral.identifier.uuidString + + var serviceUuids: [String] = [] + if let adServices = advertisementData?[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { + serviceUuids = adServices.map { $0.uuidString } + } else if let services = peripheral.services { + serviceUuids = services.map { $0.uuid.uuidString } + } + + var manufacturerData: String? = nil + if let data = advertisementData?[CBAdvertisementDataManufacturerDataKey] as? Data { + manufacturerData = data.base64EncodedString() + } + + let isConnectable = advertisementData?[CBAdvertisementDataIsConnectable] as? Bool + + let mtu = peripheral.maximumWriteValueLength(for: .withoutResponse) + 3 + + return PeripheralSnapshot( + id: id, + name: name, + rssi: rssi, + mtu: mtu, + isConnectable: isConnectable, + serviceUuids: serviceUuids, + manufacturerData: manufacturerData + ) + } + + /// Create a CharacteristicSnapshot from a CBCharacteristic + /// Must be called on the CB queue. + static func snapshot( + from characteristic: CBCharacteristic, + deviceId: String + ) -> CharacteristicSnapshot { + let value = characteristic.value?.base64EncodedString() + let props = characteristic.properties + + return CharacteristicSnapshot( + deviceId: deviceId, + serviceUuid: characteristic.service?.uuid.uuidString ?? "", + uuid: characteristic.uuid.uuidString, + value: value, + isNotifying: characteristic.isNotifying, + isIndicatable: props.contains(.indicate), + isReadable: props.contains(.read), + isWritableWithResponse: props.contains(.write), + isWritableWithoutResponse: props.contains(.writeWithoutResponse) + ) + } + + /// Create a ScanResultSnapshot from peripheral + advertisement data + static func scanResult( + from peripheral: CBPeripheral, + advertisementData: [String: Any], + rssi: NSNumber + ) -> ScanResultSnapshot { + let id = peripheral.identifier.uuidString + let name = peripheral.name + ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String + + var serviceUuids: [String] = [] + if let uuids = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { + serviceUuids = uuids.map { $0.uuidString } + } + + var manufacturerData: String? = nil + if let data = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { + manufacturerData = data.base64EncodedString() + } + + return ScanResultSnapshot( + id: id, + name: name, + rssi: rssi.intValue, + serviceUuids: serviceUuids, + manufacturerData: manufacturerData + ) + } + + /// Convert a CBManagerState to the JS state string + static func stateString(from state: CBManagerState) -> String { + switch state { + case .unknown: return "Unknown" + case .resetting: return "Resetting" + case .unsupported: return "Unsupported" + case .unauthorized: return "Unauthorized" + case .poweredOff: return "PoweredOff" + case .poweredOn: return "PoweredOn" + @unknown default: return "Unknown" + } + } +} diff --git a/ios/GATTOperationQueue.swift b/ios/GATTOperationQueue.swift new file mode 100644 index 000000000..74b0d5408 --- /dev/null +++ b/ios/GATTOperationQueue.swift @@ -0,0 +1,123 @@ +import Foundation + +// MARK: - GATTOperation + +/// A single GATT operation with a typed result +final class GATTOperation: Sendable { + let id: String + let timeout: TimeInterval + let continuation: UnsafeContinuation + + init(id: String, timeout: TimeInterval, continuation: UnsafeContinuation) { + self.id = id + self.timeout = timeout + self.continuation = continuation + } +} + +// MARK: - GATTOperationQueue + +/// Actor-based serial queue for GATT operations. +/// Operations execute one at a time with configurable timeouts. +actor GATTOperationQueue { + private var isExecuting = false + private var pendingWork: [() async -> Void] = [] + private var activeTransactionId: String? + private var cancelledTransactions: Set = [] + + let defaultTimeout: TimeInterval + + init(defaultTimeout: TimeInterval = 10.0) { + self.defaultTimeout = defaultTimeout + } + + /// Enqueue an operation that will execute when the queue is free. + /// Returns the result of the operation or throws on timeout/cancellation. + func enqueue( + transactionId: String? = nil, + timeout: TimeInterval? = nil, + operation: @escaping @Sendable () async throws -> T + ) async throws -> T { + // Check if already cancelled before queuing + if let txId = transactionId, cancelledTransactions.contains(txId) { + cancelledTransactions.remove(txId) + throw BleError(code: .operationCancelled, message: "Transaction \(txId) was cancelled") + } + + return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in + let work: () async -> Void = { [weak self] in + guard let self = self else { + continuation.resume(throwing: BleError(code: .bluetoothManagerDestroyed, message: "Queue destroyed")) + return + } + + // Re-check cancellation at execution time — cancel may have arrived + // between enqueue and when this work block actually starts running. + if let txId = transactionId, await self.isCancelled(txId) { + continuation.resume(throwing: BleError(code: .operationCancelled, message: "Transaction \(txId) was cancelled")) + await self.operationFinished() + return + } + + let effectiveTimeout = timeout ?? self.defaultTimeout + + do { + let result = try await withThrowingTaskGroup(of: T.self) { group in + group.addTask { + return try await operation() + } + group.addTask { + try await Task.sleep(nanoseconds: UInt64(effectiveTimeout * 1_000_000_000)) + throw BleError(code: .operationTimedOut, message: "Operation timed out after \(effectiveTimeout)s") + } + + guard let result = try await group.next() else { + throw BleError(code: .unknown, message: "No result from operation") + } + group.cancelAll() + return result + } + continuation.resume(returning: result) + } catch { + continuation.resume(throwing: error) + } + + await self.operationFinished() + } + + pendingWork.append(work) + if let txId = transactionId { + activeTransactionId = txId + } + + if !isExecuting { + executeNext() + } + } + } + + /// Cancel a pending or active operation by transaction ID + func cancelTransaction(_ transactionId: String) { + cancelledTransactions.insert(transactionId) + } + + /// Check if a transaction has been cancelled + func isCancelled(_ transactionId: String) -> Bool { + return cancelledTransactions.contains(transactionId) + } + + private func operationFinished() { + isExecuting = false + activeTransactionId = nil + executeNext() + } + + private func executeNext() { + guard !isExecuting, !pendingWork.isEmpty else { return } + isExecuting = true + let work = pendingWork.removeFirst() + Task { + await work() + } + } +} diff --git a/ios/L2CAPManager.swift b/ios/L2CAPManager.swift new file mode 100644 index 000000000..5bf74577c --- /dev/null +++ b/ios/L2CAPManager.swift @@ -0,0 +1,229 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - L2CAPChannelWrapper + +/// Wraps a CBL2CAPChannel with stream delegate bridging. +/// L2CAP does NOT wake suspended apps. +final class L2CAPChannelWrapper: NSObject, StreamDelegate, @unchecked Sendable { + let channelId: Int + private let channel: CBL2CAPChannel + private let inputStream: InputStream + private let outputStream: OutputStream + private let runLoopQueue: DispatchQueue + + private let dataSubject = AsyncStreamBridge() + private let closeSubject = AsyncStreamBridge() + + var dataStream: AsyncStream { dataSubject.stream } + var closeStream: AsyncStream { closeSubject.stream } + + init(channel: CBL2CAPChannel, channelId: Int) { + self.channel = channel + self.channelId = channelId + self.inputStream = channel.inputStream + self.outputStream = channel.outputStream + self.runLoopQueue = DispatchQueue(label: "com.bleplx.l2cap.\(channelId)") + super.init() + + // Set up streams on a dedicated run loop + runLoopQueue.async { [weak self] in + guard let self = self else { return } + self.inputStream.delegate = self + self.outputStream.delegate = self + + let runLoop = RunLoop.current + self.inputStream.schedule(in: runLoop, forMode: .default) + self.outputStream.schedule(in: runLoop, forMode: .default) + + self.inputStream.open() + self.outputStream.open() + + // Keep the run loop alive + while !self.inputStream.streamStatus.isTerminal && + !self.outputStream.streamStatus.isTerminal { + runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: 0.1)) + } + } + } + + func write(data: Data) throws { + guard outputStream.hasSpaceAvailable else { + throw BleError(code: .l2capWriteFailed, message: "L2CAP output stream not ready") + } + + // Loop to handle partial writes — outputStream.write() may return + // less than data.count without returning -1 (not an error, just + // the output buffer being full). We must write remaining bytes. + var offset = 0 + while offset < data.count { + let bytesWritten = data.withUnsafeBytes { buffer -> Int in + guard let ptr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return -1 } + return outputStream.write(ptr.advanced(by: offset), maxLength: data.count - offset) + } + + if bytesWritten < 0 { + throw BleError( + code: .l2capWriteFailed, + message: "L2CAP write failed: \(outputStream.streamError?.localizedDescription ?? "unknown error")" + ) + } + + if bytesWritten == 0 { + // Stream buffer full — caller should retry + throw BleError(code: .l2capWriteFailed, message: "L2CAP output buffer full, wrote \(offset)/\(data.count) bytes") + } + + offset += bytesWritten + } + } + + func close() { + // Dispatch stream unschedule/close to the same RunLoop thread that scheduled them. + // Calling remove(from: .current) from a different thread is a no-op at best, + // and a race at worst. + runLoopQueue.async { [weak self] in + guard let self = self else { return } + self.inputStream.remove(from: .current, forMode: .default) + self.outputStream.remove(from: .current, forMode: .default) + self.inputStream.close() + self.outputStream.close() + } + dataSubject.finish() + closeSubject.finish() + } + + // MARK: - StreamDelegate + + func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + switch eventCode { + case .hasBytesAvailable: + guard let inputStream = aStream as? InputStream else { return } + readAvailableData(from: inputStream) + + case .errorOccurred: + let error = aStream.streamError + closeSubject.yield(error) + + case .endEncountered: + closeSubject.yield(nil) + + default: + break + } + } + + private func readAvailableData(from stream: InputStream) { + let bufferSize = 4096 + var buffer = [UInt8](repeating: 0, count: bufferSize) + var allData = Data() + + while stream.hasBytesAvailable { + let bytesRead = stream.read(&buffer, maxLength: bufferSize) + if bytesRead > 0 { + allData.append(buffer, count: bytesRead) + } else { + break + } + } + + if !allData.isEmpty { + dataSubject.yield(allData) + } + } +} + +private extension Stream.Status { + var isTerminal: Bool { + self == .closed || self == .error || self == .atEnd + } +} + +// MARK: - L2CAPManager + +/// Manages L2CAP channel lifecycle. +actor L2CAPManager { + private var channels: [Int: L2CAPChannelWrapper] = [:] + private var nextChannelId: Int = 1 + private var dataForwardingTasks: [Int: Task] = [:] + private var closeForwardingTasks: [Int: Task] = [:] + + private let onData: @Sendable (Int, String) -> Void // channelId, base64 data + private let onClose: @Sendable (Int, String?) -> Void // channelId, error message + + init( + onData: @escaping @Sendable (Int, String) -> Void, + onClose: @escaping @Sendable (Int, String?) -> Void + ) { + self.onData = onData + self.onClose = onClose + } + + /// Open a new L2CAP channel. + /// The actual CBL2CAPChannel is received via the peripheral delegate callback. + func registerChannel(_ channel: CBL2CAPChannel) -> Int { + let channelId = nextChannelId + nextChannelId += 1 + + let wrapper = L2CAPChannelWrapper(channel: channel, channelId: channelId) + channels[channelId] = wrapper + + // Forward data events + let onData = self.onData + dataForwardingTasks[channelId] = Task { [weak self] in + for await data in wrapper.dataStream { + let base64 = data.base64EncodedString() + onData(wrapper.channelId, base64) + } + } + + // Forward close events + let onClose = self.onClose + closeForwardingTasks[channelId] = Task { [weak self] in + for await error in wrapper.closeStream { + onClose(wrapper.channelId, error?.localizedDescription) + await self?.removeChannel(wrapper.channelId) + } + } + + return channelId + } + + func write(channelId: Int, data: Data) throws { + guard let channel = channels[channelId] else { + throw BleError(code: .l2capNotConnected, message: "L2CAP channel \(channelId) not found") + } + try channel.write(data: data) + } + + func close(channelId: Int) throws { + guard let channel = channels[channelId] else { + throw BleError(code: .l2capNotConnected, message: "L2CAP channel \(channelId) not found") + } + channel.close() + removeChannel(channelId) + } + + func cleanup() { + for (_, channel) in channels { + channel.close() + } + for (_, task) in dataForwardingTasks { + task.cancel() + } + for (_, task) in closeForwardingTasks { + task.cancel() + } + channels.removeAll() + dataForwardingTasks.removeAll() + closeForwardingTasks.removeAll() + } + + private func removeChannel(_ channelId: Int) { + channels.removeValue(forKey: channelId) + dataForwardingTasks[channelId]?.cancel() + dataForwardingTasks.removeValue(forKey: channelId) + closeForwardingTasks[channelId]?.cancel() + closeForwardingTasks.removeValue(forKey: channelId) + } +} diff --git a/ios/PeripheralDelegate.swift b/ios/PeripheralDelegate.swift new file mode 100644 index 000000000..74022a6e3 --- /dev/null +++ b/ios/PeripheralDelegate.swift @@ -0,0 +1,284 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - Characteristic update result + +struct CharacteristicUpdateResult: Sendable { + let characteristicUuid: String + let serviceUuid: String + let value: String? // Base64 + let error: Error? +} + +struct DiscoveryResult: Sendable { + let error: Error? +} + +struct WriteResult: Sendable { + let characteristicUuid: String + let serviceUuid: String + let error: Error? +} + +struct NotifyResult: Sendable { + let characteristicUuid: String + let serviceUuid: String + let isNotifying: Bool + let error: Error? +} + +struct RSSIResult: Sendable { + let rssi: Int + let error: Error? +} + +// MARK: - PeripheralDelegate + +/// Bridges CBPeripheralDelegate callbacks to async/await patterns. +final class PeripheralDelegate: NSObject, CBPeripheralDelegate, Sendable { + + private let deviceId: String + + // Service discovery + private let serviceDiscoverySubject = AsyncStreamBridge() + + // Characteristic discovery + private let characteristicDiscoverySubject = AsyncStreamBridge() + + // Characteristic read/notify + private let characteristicUpdateSubject = AsyncStreamBridge() + + // Characteristic write + private let writeSubject = AsyncStreamBridge() + + // Notify state + private let notifySubject = AsyncStreamBridge() + + // RSSI + private let rssiSubject = AsyncStreamBridge() + + // Write without response flow control + private let writeWithoutResponseSubject = AsyncStreamBridge() + + // L2CAP channel open + let pendingL2CAPOpen = LockedBox?>(nil) + + // Pending continuations for one-shot operations + private let pendingServiceDiscovery = LockedBox?>(nil) + private let pendingCharacteristicDiscovery = LockedDictionary>() + private let pendingReads = LockedDictionary>() + private let pendingWrites = LockedDictionary>() + private let pendingNotifyChanges = LockedDictionary>() + private let pendingRSSI = LockedBox?>(nil) + + var characteristicUpdateStream: AsyncStream { + characteristicUpdateSubject.stream + } + + init(deviceId: String) { + self.deviceId = deviceId + super.init() + } + + // MARK: - Continuation management + + func addServiceDiscoveryContinuation(_ continuation: CheckedContinuation) { + pendingServiceDiscovery.value = continuation + } + + func addCharacteristicDiscoveryContinuation(_ continuation: CheckedContinuation, for serviceUuid: String) { + pendingCharacteristicDiscovery.set(serviceUuid, value: continuation) + } + + func addReadContinuation(_ continuation: CheckedContinuation, for key: String) { + pendingReads.set(key, value: continuation) + } + + func addWriteContinuation(_ continuation: CheckedContinuation, for key: String) { + pendingWrites.set(key, value: continuation) + } + + func addNotifyContinuation(_ continuation: CheckedContinuation, for key: String) { + pendingNotifyChanges.set(key, value: continuation) + } + + func addRSSIContinuation(_ continuation: CheckedContinuation) { + pendingRSSI.value = continuation + } + + func addL2CAPOpenContinuation(_ continuation: CheckedContinuation) { + pendingL2CAPOpen.value = continuation + } + + // MARK: - CBPeripheralDelegate — Service Discovery + + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + if let continuation = pendingServiceDiscovery.value { + pendingServiceDiscovery.value = nil + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + operation: "discoverServices" + )) + } else { + continuation.resume() + } + } + } + + func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + let key = service.uuid.uuidString + if let continuation = pendingCharacteristicDiscovery.removeValue(forKey: key) { + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + serviceUuid: key, + operation: "discoverCharacteristics" + )) + } else { + continuation.resume() + } + } + } + + // MARK: - CBPeripheralDelegate — Characteristic Operations + + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + let charKey = characteristicKey(characteristic) + + // Check if this is a pending read + if let continuation = pendingReads.removeValue(forKey: charKey) { + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + serviceUuid: characteristic.service?.uuid.uuidString, + characteristicUuid: characteristic.uuid.uuidString, + operation: "read" + )) + } else { + let snapshot = EventSerializer.snapshot(from: characteristic, deviceId: deviceId) + continuation.resume(returning: snapshot) + } + return + } + + // Otherwise it's a notification + characteristicUpdateSubject.yield(CharacteristicUpdateResult( + characteristicUuid: characteristic.uuid.uuidString, + serviceUuid: characteristic.service?.uuid.uuidString ?? "", + value: characteristic.value?.base64EncodedString(), + error: error + )) + } + + func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { + let charKey = characteristicKey(characteristic) + if let continuation = pendingWrites.removeValue(forKey: charKey) { + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + serviceUuid: characteristic.service?.uuid.uuidString, + characteristicUuid: characteristic.uuid.uuidString, + operation: "write" + )) + } else { + let snapshot = EventSerializer.snapshot(from: characteristic, deviceId: deviceId) + continuation.resume(returning: snapshot) + } + } + } + + func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { + let charKey = characteristicKey(characteristic) + if let continuation = pendingNotifyChanges.removeValue(forKey: charKey) { + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + serviceUuid: characteristic.service?.uuid.uuidString, + characteristicUuid: characteristic.uuid.uuidString, + operation: "notify" + )) + } else { + continuation.resume() + } + } + } + + // MARK: - CBPeripheralDelegate — RSSI + + func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { + if let continuation = pendingRSSI.value { + pendingRSSI.value = nil + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + operation: "readRSSI" + )) + } else { + continuation.resume(returning: RSSI.intValue) + } + } + } + + // MARK: - CBPeripheralDelegate — L2CAP + + func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?) { + let continuation: CheckedContinuation? = pendingL2CAPOpen.mutate { current in + let taken = current + current = nil + return taken + } + if let continuation = continuation { + if let error = error { + continuation.resume(throwing: ErrorConverter.from( + cbError: error, + deviceId: deviceId, + operation: "openL2CAPChannel" + )) + } else if let channel = channel { + continuation.resume(returning: channel) + } else { + continuation.resume(throwing: BleError( + code: .l2capOpenFailed, + message: "L2CAP channel open returned nil channel without error", + deviceId: deviceId + )) + } + } + } + + // MARK: - CBPeripheralDelegate — Write without response + + func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) { + writeWithoutResponseSubject.yield(()) + } + + var writeWithoutResponseReadyStream: AsyncStream { + writeWithoutResponseSubject.stream + } + + // MARK: - Cleanup + + func finish() { + serviceDiscoverySubject.finish() + characteristicDiscoverySubject.finish() + characteristicUpdateSubject.finish() + writeSubject.finish() + notifySubject.finish() + rssiSubject.finish() + writeWithoutResponseSubject.finish() + } + + // MARK: - Helpers + + private func characteristicKey(_ characteristic: CBCharacteristic) -> String { + let serviceUuid = characteristic.service?.uuid.uuidString ?? "" + return "\(serviceUuid)|\(characteristic.uuid.uuidString)" + } +} diff --git a/ios/PeripheralWrapper.swift b/ios/PeripheralWrapper.swift new file mode 100644 index 000000000..9166d2c5f --- /dev/null +++ b/ios/PeripheralWrapper.swift @@ -0,0 +1,218 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - PeripheralWrapper + +/// Per-peripheral actor that owns the CBPeripheral, its delegate, and a GATT operation queue. +/// Uses a custom executor pinned to the CB dispatch queue for thread safety. +actor PeripheralWrapper { + let queue: DispatchSerialQueue + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + private let peripheral: CBPeripheral + private let delegate: PeripheralDelegate + private let gattQueue: GATTOperationQueue + let deviceId: String + + /// Stream of characteristic notifications for this peripheral + var characteristicUpdateStream: AsyncStream { + delegate.characteristicUpdateStream + } + + init(peripheral: CBPeripheral, queue: DispatchSerialQueue) { + self.peripheral = peripheral + self.queue = queue + self.deviceId = peripheral.identifier.uuidString + self.delegate = PeripheralDelegate(deviceId: peripheral.identifier.uuidString) + self.gattQueue = GATTOperationQueue() + peripheral.delegate = delegate + } + + // MARK: - Discovery + + func discoverAllServicesAndCharacteristics() async throws -> PeripheralSnapshot { + // Discover services + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addServiceDiscoveryContinuation(continuation) + peripheral.discoverServices(nil) + } + + // Discover characteristics for each service + if let services = peripheral.services { + for service in services { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addCharacteristicDiscoveryContinuation(continuation, for: service.uuid.uuidString) + peripheral.discoverCharacteristics(nil, for: service) + } + } + } + + return EventSerializer.snapshot(from: peripheral) + } + + // MARK: - Read + + func readCharacteristic( + serviceUuid: String, + characteristicUuid: String, + transactionId: String? + ) async throws -> CharacteristicSnapshot { + guard let characteristic = findCharacteristic(serviceUuid: serviceUuid, characteristicUuid: characteristicUuid) else { + throw BleError( + code: .characteristicsNotDiscovered, + message: "Characteristic \(characteristicUuid) not found in service \(serviceUuid)", + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid + ) + } + + let charKey = "\(serviceUuid)|\(characteristicUuid)" + + return try await gattQueue.enqueue(transactionId: transactionId) { [peripheral, delegate, deviceId] in + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addReadContinuation(continuation, for: charKey) + peripheral.readValue(for: characteristic) + } + } + } + + // MARK: - Write + + func writeCharacteristic( + serviceUuid: String, + characteristicUuid: String, + value: Data, + withResponse: Bool, + transactionId: String? + ) async throws -> CharacteristicSnapshot { + guard let characteristic = findCharacteristic(serviceUuid: serviceUuid, characteristicUuid: characteristicUuid) else { + throw BleError( + code: .characteristicsNotDiscovered, + message: "Characteristic \(characteristicUuid) not found in service \(serviceUuid)", + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid + ) + } + + let charKey = "\(serviceUuid)|\(characteristicUuid)" + let writeType: CBCharacteristicWriteType = withResponse ? .withResponse : .withoutResponse + + if withResponse { + return try await gattQueue.enqueue(transactionId: transactionId) { [peripheral, delegate, deviceId] in + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addWriteContinuation(continuation, for: charKey) + peripheral.writeValue(value, for: characteristic, type: writeType) + } + } + } else { + // Write without response: check canSendWriteWithoutResponse + return try await gattQueue.enqueue(transactionId: transactionId) { [peripheral, delegate, deviceId] in + if !peripheral.canSendWriteWithoutResponse { + // Wait for ready signal + for await _ in delegate.writeWithoutResponseReadyStream { + break + } + } + peripheral.writeValue(value, for: characteristic, type: writeType) + return EventSerializer.snapshot(from: characteristic, deviceId: deviceId) + } + } + } + + // MARK: - Monitor (Notify/Indicate) + + func setNotifyValue(_ enabled: Bool, serviceUuid: String, characteristicUuid: String) async throws { + guard let characteristic = findCharacteristic(serviceUuid: serviceUuid, characteristicUuid: characteristicUuid) else { + throw BleError( + code: .characteristicsNotDiscovered, + message: "Characteristic \(characteristicUuid) not found", + deviceId: deviceId, + serviceUuid: serviceUuid, + characteristicUuid: characteristicUuid + ) + } + + let charKey = "\(serviceUuid)|\(characteristicUuid)" + + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addNotifyContinuation(continuation, for: charKey) + peripheral.setNotifyValue(enabled, for: characteristic) + } + } + + // MARK: - RSSI + + func readRSSI() async throws -> Int { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addRSSIContinuation(continuation) + peripheral.readRSSI() + } + } + + // MARK: - MTU + + func mtu() -> Int { + peripheral.maximumWriteValueLength(for: .withoutResponse) + 3 + } + + // MARK: - Snapshot + + func snapshot() -> PeripheralSnapshot { + EventSerializer.snapshot(from: peripheral) + } + + // MARK: - Connection state + + func isConnected() -> Bool { + peripheral.state == .connected + } + + // MARK: - Cancel + + func cancelTransaction(_ transactionId: String) async { + await gattQueue.cancelTransaction(transactionId) + } + + // MARK: - Cleanup + + func cleanup() { + delegate.finish() + } + + // MARK: - L2CAP + + @available(iOS 11.0, *) + func openL2CAPChannel(psm: CBL2CAPPSM) async throws -> CBL2CAPChannel { + // Guard against concurrent opens + guard delegate.pendingL2CAPOpen.value == nil else { + throw BleError( + code: .l2capOpenFailed, + message: "L2CAP channel open already in progress", + deviceId: deviceId + ) + } + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + delegate.addL2CAPOpenContinuation(continuation) + peripheral.openL2CAPChannel(psm) + } + } + + // MARK: - Internal + + /// The underlying CBPeripheral — only access from the correct queue + var cbPeripheral: CBPeripheral { peripheral } + + // MARK: - Private helpers + + private func findCharacteristic(serviceUuid: String, characteristicUuid: String) -> CBCharacteristic? { + let targetServiceUuid = CBUUID(string: serviceUuid) + let targetCharUuid = CBUUID(string: characteristicUuid) + + guard let service = peripheral.services?.first(where: { $0.uuid == targetServiceUuid }) else { + return nil + } + return service.characteristics?.first(where: { $0.uuid == targetCharUuid }) + } +} diff --git a/ios/ScanManager.swift b/ios/ScanManager.swift new file mode 100644 index 000000000..98fb5fde7 --- /dev/null +++ b/ios/ScanManager.swift @@ -0,0 +1,66 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - ScanManager + +/// Manages BLE scanning using AsyncStream. +/// Scan results are yielded as ScanResultSnapshot values. +actor ScanManager { + private var isScanning = false + private var scanTask: Task? + + let queue: DispatchSerialQueue + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + init(queue: DispatchSerialQueue) { + self.queue = queue + } + + /// Start scanning for peripherals. + /// The `onResult` callback is called for each discovered device. + func startScan( + centralManager: CBCentralManager, + serviceUuids: [CBUUID]?, + options: [String: Any]?, + delegateHandler: CentralManagerDelegate, + onResult: @escaping @Sendable (ScanResultSnapshot) -> Void + ) { + stopScanInternal(centralManager: centralManager) + + isScanning = true + + // Start the CB scan + centralManager.scanForPeripherals( + withServices: serviceUuids, + options: options + ) + + // Consume the scan stream + scanTask = Task { [weak self] in + for await result in delegateHandler.scanStream { + guard let self = self else { break } + // Check we're still scanning (actor-isolated check done via Task) + guard !Task.isCancelled else { break } + onResult(result) + } + } + } + + /// Stop scanning + func stopScan(centralManager: CBCentralManager) { + stopScanInternal(centralManager: centralManager) + } + + private func stopScanInternal(centralManager: CBCentralManager) { + if isScanning { + centralManager.stopScan() + isScanning = false + } + scanTask?.cancel() + scanTask = nil + } + + func cleanup(centralManager: CBCentralManager) { + stopScanInternal(centralManager: centralManager) + } +} diff --git a/ios/StateRestoration.swift b/ios/StateRestoration.swift new file mode 100644 index 000000000..b448aa000 --- /dev/null +++ b/ios/StateRestoration.swift @@ -0,0 +1,111 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - RestoredPeripheralInfo + +struct RestoredPeripheralInfo: Sendable { + let id: String + let name: String? +} + +// MARK: - RestorationState + +/// Sendable replacement for the raw `[String: Any]` dictionary from willRestoreState. +/// Parsed immediately in the delegate callback (CB queue isolation domain) so that +/// no non-Sendable dictionary crosses actor boundaries. +struct RestorationState: Sendable { + let peripheralIdentifiers: [UUID] + let scanServiceUUIDs: [String]? + let scanOptions: [String: Bool]? + + init(from dict: [String: Any]) { + if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] { + self.peripheralIdentifiers = peripherals.map { $0.identifier } + } else { + self.peripheralIdentifiers = [] + } + + if let serviceUUIDs = dict[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID] { + self.scanServiceUUIDs = serviceUUIDs.map { $0.uuidString } + } else { + self.scanServiceUUIDs = nil + } + + if let options = dict[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any] { + var boolOptions: [String: Bool] = [:] + for (key, value) in options { + if let boolValue = value as? Bool { + boolOptions[key] = boolValue + } + } + self.scanOptions = boolOptions.isEmpty ? nil : boolOptions + } else { + self.scanOptions = nil + } + } +} + +// MARK: - StateRestoration + +/// Handles CoreBluetooth state restoration for background BLE operations. +/// - Re-attaches delegates to restored peripherals +/// - Stores peripheral references strongly (CB does not retain them) +/// - Buffers restoration data until JS subscribes +actor StateRestoration { + private var restoredPeripherals: [UUID: CBPeripheral] = [:] + private var bufferedRestorationData: [RestorationState] = [] + private var hasJSSubscribed = false + + let queue: DispatchSerialQueue + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + init(queue: DispatchSerialQueue) { + self.queue = queue + } + + /// Process restoration data from willRestoreState. + /// Called before didUpdateState, so we must buffer. + func handleRestoration( + dict: [String: Any], + peripheralFactory: (CBPeripheral) -> PeripheralWrapper + ) -> [PeripheralWrapper] { + var wrappers: [PeripheralWrapper] = [] + + if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] { + for peripheral in peripherals { + // Store strong reference — CB does NOT retain restored peripherals + restoredPeripherals[peripheral.identifier] = peripheral + + let wrapper = peripheralFactory(peripheral) + wrappers.append(wrapper) + } + } + + // Buffer the parsed (Sendable) restoration data for when JS subscribes + bufferedRestorationData.append(RestorationState(from: dict)) + + return wrappers + } + + /// Called when JS subscribes to restoration events. + /// Returns any buffered data, then clears the buffer. + func getBufferedRestorationData() -> [RestorationState] { + hasJSSubscribed = true + let data = bufferedRestorationData + bufferedRestorationData.removeAll() + return data + } + + /// Get restored peripheral info for the JS RestoreStateEvent + func getRestoredDeviceInfos() -> [PeripheralSnapshot] { + restoredPeripherals.values.map { peripheral in + EventSerializer.snapshot(from: peripheral) + } + } + + /// Clean up restored peripheral references + func cleanup() { + restoredPeripherals.removeAll() + bufferedRestorationData.removeAll() + } +} diff --git a/ios/Tests/ErrorConverterTests.swift b/ios/Tests/ErrorConverterTests.swift new file mode 100644 index 000000000..6f7447794 --- /dev/null +++ b/ios/Tests/ErrorConverterTests.swift @@ -0,0 +1,235 @@ +import XCTest +import CoreBluetooth +@testable import BlePlx + +/// Tests for CBError / CBATTError → BleError mapping in ErrorConverter. +final class ErrorConverterTests: XCTestCase { + + // MARK: - CBError → BleErrorCode + + func testConnectionFailedMapsToBleCodeDeviceConnectionFailed() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionFailed.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .deviceConnectionFailed) + } + + func testConnectionFailedIsRetryable() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionFailed.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertTrue(ble.isRetryable) + } + + func testNotConnectedMapsToDeviceNotConnected() { + let error = nsError(domain: CBErrorDomain, code: CBError.notConnected.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .deviceNotConnected) + } + + func testNotConnectedIsNotRetryable() { + let error = nsError(domain: CBErrorDomain, code: CBError.notConnected.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertFalse(ble.isRetryable) + } + + func testPeerRemovedPairingInformationMapsToDeviceBondLost() { + let error = nsError(domain: CBErrorDomain, code: CBError.peerRemovedPairingInformation.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .deviceBondLost) + } + + func testPeerRemovedPairingInformationIsNotRetryable() { + let error = nsError(domain: CBErrorDomain, code: CBError.peerRemovedPairingInformation.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertFalse(ble.isRetryable) + } + + func testConnectionLimitReachedMapsToDeviceConnectionFailed() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionLimitReached.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .deviceConnectionFailed) + } + + func testConnectionLimitReachedIsRetryable() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionLimitReached.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertTrue(ble.isRetryable) + } + + func testOperationNotSupportedMapsToOperationStartFailed() { + let error = nsError(domain: CBErrorDomain, code: CBError.operationNotSupported.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .operationStartFailed) + } + + func testUnknownCBErrorMapsToUnknown() { + let error = nsError(domain: CBErrorDomain, code: CBError.unknown.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .unknown) + } + + // MARK: - CBError context forwarding + + func testCBErrorForwardsDeviceId() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionFailed.rawValue) + let ble = ErrorConverter.from(cbError: error, deviceId: "device-123") + XCTAssertEqual(ble.deviceId, "device-123") + } + + func testCBErrorForwardsOperation() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionFailed.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "connect") + XCTAssertEqual(ble.operation, "connect") + } + + func testCBErrorSetsDomain() { + let error = nsError(domain: CBErrorDomain, code: CBError.notConnected.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.nativeDomain, CBErrorDomain) + } + + func testCBErrorSetsNativeCode() { + let error = nsError(domain: CBErrorDomain, code: CBError.connectionFailed.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.nativeCode, CBError.connectionFailed.rawValue) + } + + // MARK: - CBATTError → BleErrorCode + + func testATTErrorWithReadOperationMapsToCharacteristicReadFailed() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.readNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "read") + XCTAssertEqual(ble.code, .characteristicReadFailed) + } + + func testATTErrorWithWriteOperationMapsToCharacteristicWriteFailed() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.writeNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "write") + XCTAssertEqual(ble.code, .characteristicWriteFailed) + } + + func testATTErrorWithNotifyOperationMapsToCharacteristicNotifyChangeFailed() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.attributeNotFound.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "notify") + XCTAssertEqual(ble.code, .characteristicNotifyChangeFailed) + } + + func testATTErrorWithReadDescriptorOperationMapsToDescriptorReadFailed() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.readNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "readDescriptor") + XCTAssertEqual(ble.code, .descriptorReadFailed) + } + + func testATTErrorWithWriteDescriptorOperationMapsToDescriptorWriteFailed() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.writeNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "writeDescriptor") + XCTAssertEqual(ble.code, .descriptorWriteFailed) + } + + func testATTErrorWithNoOperationMapsToUnknown() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.readNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .unknown) + } + + func testATTErrorSetsAttErrorCode() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.insufficientAuthentication.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "read") + XCTAssertEqual(ble.attErrorCode, CBATTError.insufficientAuthentication.rawValue) + } + + func testATTErrorSetsDomain() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.readNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "read") + XCTAssertEqual(ble.nativeDomain, CBATTErrorDomain) + } + + // MARK: - isRetryable for ATT errors + + func testATTInsufficientResourcesIsRetryable() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.insufficientResources.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "read") + XCTAssertTrue(ble.isRetryable) + } + + func testATTUnlikelyErrorIsRetryable() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.unlikelyError.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "read") + XCTAssertTrue(ble.isRetryable) + } + + func testATTReadNotPermittedIsNotRetryable() { + let error = nsError(domain: CBATTErrorDomain, code: CBATTError.readNotPermitted.rawValue) + let ble = ErrorConverter.from(cbError: error, operation: "read") + XCTAssertFalse(ble.isRetryable) + } + + // MARK: - Generic (non-CB, non-ATT) errors + + func testGenericErrorMapsToUnknown() { + let error = nsError(domain: "com.example.custom", code: 42) + let ble = ErrorConverter.from(cbError: error) + XCTAssertEqual(ble.code, .unknown) + XCTAssertFalse(ble.isRetryable) + XCTAssertEqual(ble.nativeDomain, "com.example.custom") + XCTAssertEqual(ble.nativeCode, 42) + } + + // MARK: - bleStateError + + func testPoweredOnReturnsNil() { + XCTAssertNil(ErrorConverter.bleStateError(for: .poweredOn)) + } + + func testPoweredOffReturnsBleError() { + let error = ErrorConverter.bleStateError(for: .poweredOff) + XCTAssertEqual(error?.code, .bluetoothPoweredOff) + XCTAssertFalse(error?.isRetryable ?? true) + } + + func testUnauthorizedReturnsBluetoothUnauthorized() { + let error = ErrorConverter.bleStateError(for: .unauthorized) + XCTAssertEqual(error?.code, .bluetoothUnauthorized) + } + + func testUnsupportedReturnsBluetoothUnsupported() { + let error = ErrorConverter.bleStateError(for: .unsupported) + XCTAssertEqual(error?.code, .bluetoothUnsupported) + } + + func testResettingReturnsBluetoothResettingAndIsRetryable() { + let error = ErrorConverter.bleStateError(for: .resetting) + XCTAssertEqual(error?.code, .bluetoothResetting) + XCTAssertTrue(error?.isRetryable ?? false) + } + + func testUnknownStateReturnsBluetoothInUnknownState() { + let error = ErrorConverter.bleStateError(for: .unknown) + XCTAssertEqual(error?.code, .bluetoothInUnknownState) + } + + // MARK: - BleError.toDictionary + + func testBleErrorToDictionaryContainsPlatformKey() { + let err = BleError(code: .deviceConnectionFailed, message: "test") + let dict = err.toDictionary() + XCTAssertEqual(dict["platform"] as? String, "ios") + } + + func testBleErrorToDictionaryContainsCode() { + let err = BleError(code: .deviceConnectionFailed, message: "test") + let dict = err.toDictionary() + XCTAssertEqual(dict["code"] as? Int, BleErrorCode.deviceConnectionFailed.rawValue) + } + + func testBleErrorToDictionaryContainsIsRetryable() { + let err = BleError(code: .deviceConnectionFailed, message: "test", isRetryable: true) + let dict = err.toDictionary() + XCTAssertEqual(dict["isRetryable"] as? Bool, true) + } + + // MARK: - Helpers + + private func nsError(domain: String, code: Int) -> NSError { + return NSError(domain: domain, code: code, userInfo: nil) + } +} diff --git a/ios/Tests/EventSerializerTests.swift b/ios/Tests/EventSerializerTests.swift new file mode 100644 index 000000000..d46352d58 --- /dev/null +++ b/ios/Tests/EventSerializerTests.swift @@ -0,0 +1,302 @@ +import XCTest +import CoreBluetooth +@testable import BlePlx + +/// Tests for EventSerializer — CoreBluetooth object → Sendable snapshot → dictionary. +/// +/// EventSerializer doesn't accept CBPeripheral directly in the parts we test +/// (PeripheralSnapshot, CharacteristicSnapshot, ScanResultSnapshot toDictionary), +/// so many tests work entirely with the concrete structs returned by the +/// serializer helpers and verify the resulting dictionaries. +final class EventSerializerTests: XCTestCase { + + // MARK: - PeripheralSnapshot.toDictionary + + func testPeripheralSnapshotHasIdKey() { + let snapshot = makePeripheralSnapshot(id: "AABB-CCDD") + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["id"] as? String, "AABB-CCDD") + } + + func testPeripheralSnapshotHasNameKey() { + let snapshot = makePeripheralSnapshot(name: "HeartMonitor") + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["name"] as? String, "HeartMonitor") + } + + func testPeripheralSnapshotNameIsNilWhenNotProvided() { + let snapshot = makePeripheralSnapshot(name: nil) + let dict = snapshot.toDictionary() + // The dict stores nil as Any, so the value is present but nil + XCTAssertTrue(dict.keys.contains("name"), "name key must always be present") + XCTAssertNil(dict["name"] as? String) + } + + func testPeripheralSnapshotHasRssi() { + let snapshot = makePeripheralSnapshot(rssi: -72) + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["rssi"] as? Int, -72) + } + + func testPeripheralSnapshotHasMtu() { + let snapshot = makePeripheralSnapshot(mtu: 185) + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["mtu"] as? Int, 185) + } + + func testPeripheralSnapshotIsConnectableTrue() { + let snapshot = makePeripheralSnapshot(isConnectable: true) + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["isConnectable"] as? Bool, true) + } + + func testPeripheralSnapshotIsConnectableNilWhenNotSet() { + let snapshot = makePeripheralSnapshot(isConnectable: nil) + let dict = snapshot.toDictionary() + XCTAssertTrue(dict.keys.contains("isConnectable")) + XCTAssertNil(dict["isConnectable"] as? Bool) + } + + func testPeripheralSnapshotServiceUuidsArePresent() { + let uuids = ["180D", "180F"] + let snapshot = makePeripheralSnapshot(serviceUuids: uuids) + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["serviceUuids"] as? [String], uuids) + } + + func testPeripheralSnapshotEmptyServiceUuids() { + let snapshot = makePeripheralSnapshot(serviceUuids: []) + let dict = snapshot.toDictionary() + XCTAssertEqual((dict["serviceUuids"] as? [String])?.count, 0) + } + + func testPeripheralSnapshotManufacturerDataBase64() { + let b64 = Data([0x01, 0x02, 0x03]).base64EncodedString() + let snapshot = makePeripheralSnapshot(manufacturerData: b64) + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["manufacturerData"] as? String, b64) + } + + func testPeripheralSnapshotManufacturerDataNilWhenAbsent() { + let snapshot = makePeripheralSnapshot(manufacturerData: nil) + let dict = snapshot.toDictionary() + XCTAssertTrue(dict.keys.contains("manufacturerData")) + XCTAssertNil(dict["manufacturerData"] as? String) + } + + // MARK: - CharacteristicSnapshot.toDictionary + + func testCharacteristicSnapshotHasRequiredKeys() { + let snapshot = makeCharacteristicSnapshot() + let dict = snapshot.toDictionary() + let expected = ["deviceId", "serviceUuid", "uuid", "value", + "isNotifying", "isIndicatable", "isReadable", + "isWritableWithResponse", "isWritableWithoutResponse"] + for key in expected { + XCTAssertTrue(dict.keys.contains(key), "Missing key: \(key)") + } + } + + func testCharacteristicSnapshotDeviceId() { + let snapshot = makeCharacteristicSnapshot(deviceId: "dev-99") + XCTAssertEqual(snapshot.toDictionary()["deviceId"] as? String, "dev-99") + } + + func testCharacteristicSnapshotServiceUuid() { + let snapshot = makeCharacteristicSnapshot(serviceUuid: "180A") + XCTAssertEqual(snapshot.toDictionary()["serviceUuid"] as? String, "180A") + } + + func testCharacteristicSnapshotUuid() { + let snapshot = makeCharacteristicSnapshot(uuid: "2A37") + XCTAssertEqual(snapshot.toDictionary()["uuid"] as? String, "2A37") + } + + func testCharacteristicSnapshotValueBase64() { + let b64 = Data([0xDE, 0xAD]).base64EncodedString() + let snapshot = makeCharacteristicSnapshot(value: b64) + XCTAssertEqual(snapshot.toDictionary()["value"] as? String, b64) + } + + func testCharacteristicSnapshotValueNil() { + let snapshot = makeCharacteristicSnapshot(value: nil) + let dict = snapshot.toDictionary() + XCTAssertTrue(dict.keys.contains("value")) + XCTAssertNil(dict["value"] as? String) + } + + func testCharacteristicSnapshotIsNotifying() { + let snapshot = makeCharacteristicSnapshot(isNotifying: true) + XCTAssertEqual(snapshot.toDictionary()["isNotifying"] as? Bool, true) + } + + func testCharacteristicSnapshotIsNotNotifying() { + let snapshot = makeCharacteristicSnapshot(isNotifying: false) + XCTAssertEqual(snapshot.toDictionary()["isNotifying"] as? Bool, false) + } + + func testCharacteristicSnapshotIsReadable() { + let snapshot = makeCharacteristicSnapshot(isReadable: true) + XCTAssertEqual(snapshot.toDictionary()["isReadable"] as? Bool, true) + } + + func testCharacteristicSnapshotIsIndicatable() { + let snapshot = makeCharacteristicSnapshot(isIndicatable: true) + XCTAssertEqual(snapshot.toDictionary()["isIndicatable"] as? Bool, true) + } + + func testCharacteristicSnapshotIsWritableWithResponse() { + let snapshot = makeCharacteristicSnapshot(isWritableWithResponse: true) + XCTAssertEqual(snapshot.toDictionary()["isWritableWithResponse"] as? Bool, true) + } + + func testCharacteristicSnapshotIsWritableWithoutResponse() { + let snapshot = makeCharacteristicSnapshot(isWritableWithoutResponse: true) + XCTAssertEqual(snapshot.toDictionary()["isWritableWithoutResponse"] as? Bool, true) + } + + func testCharacteristicSnapshotAllPropertiesFalseByDefault() { + let snapshot = makeCharacteristicSnapshot() + let dict = snapshot.toDictionary() + XCTAssertEqual(dict["isIndicatable"] as? Bool, false) + XCTAssertEqual(dict["isReadable"] as? Bool, false) + XCTAssertEqual(dict["isWritableWithResponse"] as? Bool, false) + XCTAssertEqual(dict["isWritableWithoutResponse"] as? Bool, false) + } + + // MARK: - ScanResultSnapshot.toDictionary + + func testScanResultSnapshotHasId() { + let snapshot = makeScanResultSnapshot(id: "scan-id-1") + XCTAssertEqual(snapshot.toDictionary()["id"] as? String, "scan-id-1") + } + + func testScanResultSnapshotHasName() { + let snapshot = makeScanResultSnapshot(name: "Beacon") + XCTAssertEqual(snapshot.toDictionary()["name"] as? String, "Beacon") + } + + func testScanResultSnapshotNameNilWhenAbsent() { + let snapshot = makeScanResultSnapshot(name: nil) + let dict = snapshot.toDictionary() + XCTAssertTrue(dict.keys.contains("name")) + XCTAssertNil(dict["name"] as? String) + } + + func testScanResultSnapshotHasRssi() { + let snapshot = makeScanResultSnapshot(rssi: -55) + XCTAssertEqual(snapshot.toDictionary()["rssi"] as? Int, -55) + } + + func testScanResultSnapshotServiceUuids() { + let uuids = ["180D", "1800"] + let snapshot = makeScanResultSnapshot(serviceUuids: uuids) + XCTAssertEqual(snapshot.toDictionary()["serviceUuids"] as? [String], uuids) + } + + func testScanResultSnapshotEmptyServiceUuids() { + let snapshot = makeScanResultSnapshot(serviceUuids: []) + XCTAssertEqual((snapshot.toDictionary()["serviceUuids"] as? [String])?.count, 0) + } + + func testScanResultSnapshotManufacturerData() { + let b64 = Data([0xAB, 0xCD]).base64EncodedString() + let snapshot = makeScanResultSnapshot(manufacturerData: b64) + XCTAssertEqual(snapshot.toDictionary()["manufacturerData"] as? String, b64) + } + + func testScanResultSnapshotManufacturerDataNil() { + let snapshot = makeScanResultSnapshot(manufacturerData: nil) + let dict = snapshot.toDictionary() + XCTAssertTrue(dict.keys.contains("manufacturerData")) + XCTAssertNil(dict["manufacturerData"] as? String) + } + + // MARK: - EventSerializer.stateString + + func testStateStringUnknown() { + XCTAssertEqual(EventSerializer.stateString(from: .unknown), "Unknown") + } + + func testStateStringResetting() { + XCTAssertEqual(EventSerializer.stateString(from: .resetting), "Resetting") + } + + func testStateStringUnsupported() { + XCTAssertEqual(EventSerializer.stateString(from: .unsupported), "Unsupported") + } + + func testStateStringUnauthorized() { + XCTAssertEqual(EventSerializer.stateString(from: .unauthorized), "Unauthorized") + } + + func testStateStringPoweredOff() { + XCTAssertEqual(EventSerializer.stateString(from: .poweredOff), "PoweredOff") + } + + func testStateStringPoweredOn() { + XCTAssertEqual(EventSerializer.stateString(from: .poweredOn), "PoweredOn") + } + + // MARK: - Helpers + + private func makePeripheralSnapshot( + id: String = "test-id", + name: String? = "TestDevice", + rssi: Int = -70, + mtu: Int = 185, + isConnectable: Bool? = true, + serviceUuids: [String] = [], + manufacturerData: String? = nil + ) -> PeripheralSnapshot { + return PeripheralSnapshot( + id: id, + name: name, + rssi: rssi, + mtu: mtu, + isConnectable: isConnectable, + serviceUuids: serviceUuids, + manufacturerData: manufacturerData + ) + } + + private func makeCharacteristicSnapshot( + deviceId: String = "dev-1", + serviceUuid: String = "180A", + uuid: String = "2A37", + value: String? = nil, + isNotifying: Bool = false, + isIndicatable: Bool = false, + isReadable: Bool = false, + isWritableWithResponse: Bool = false, + isWritableWithoutResponse: Bool = false + ) -> CharacteristicSnapshot { + return CharacteristicSnapshot( + deviceId: deviceId, + serviceUuid: serviceUuid, + uuid: uuid, + value: value, + isNotifying: isNotifying, + isIndicatable: isIndicatable, + isReadable: isReadable, + isWritableWithResponse: isWritableWithResponse, + isWritableWithoutResponse: isWritableWithoutResponse + ) + } + + private func makeScanResultSnapshot( + id: String = "scan-id", + name: String? = "Scanner", + rssi: Int = -65, + serviceUuids: [String] = [], + manufacturerData: String? = nil + ) -> ScanResultSnapshot { + return ScanResultSnapshot( + id: id, + name: name, + rssi: rssi, + serviceUuids: serviceUuids, + manufacturerData: manufacturerData + ) + } +} diff --git a/ios/Tests/GATTOperationQueueTests.swift b/ios/Tests/GATTOperationQueueTests.swift new file mode 100644 index 000000000..d6336debf --- /dev/null +++ b/ios/Tests/GATTOperationQueueTests.swift @@ -0,0 +1,236 @@ +import XCTest +@testable import BlePlx + +/// Tests for the actor-based serial GATT operation queue. +/// +/// The queue provides: +/// - Serial execution: second operation waits for first to finish +/// - Per-operation timeouts that throw `.operationTimedOut` +/// - Pre-enqueue cancellation detection +/// - Mid-queue cancellation detection (cancel arrives between enqueue and execution) +final class GATTOperationQueueTests: XCTestCase { + + // MARK: - Serial execution + + /// Two operations enqueued concurrently must execute sequentially: + /// the second must not start until the first completes. + func testOperationsExecuteSerially() async throws { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + var executionOrder: [Int] = [] + let lock = NSLock() + + // First operation takes a small amount of time + async let first: Int = queue.enqueue { + try await Task.sleep(nanoseconds: 50_000_000) // 50ms + lock.lock() + executionOrder.append(1) + lock.unlock() + return 1 + } + + // Small delay to ensure first is enqueued before second + try await Task.sleep(nanoseconds: 5_000_000) // 5ms + + async let second: Int = queue.enqueue { + lock.lock() + executionOrder.append(2) + lock.unlock() + return 2 + } + + let (r1, r2) = try await (first, second) + XCTAssertEqual(r1, 1) + XCTAssertEqual(r2, 2) + XCTAssertEqual(executionOrder, [1, 2], "Second must not start before first completes") + } + + /// Enqueuing multiple operations returns each in the correct order. + func testQueueReturnValues() async throws { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + + async let a: String = queue.enqueue { "alpha" } + async let b: String = queue.enqueue { "beta" } + async let c: String = queue.enqueue { "gamma" } + + let results = try await [a, b, c] + XCTAssertEqual(results, ["alpha", "beta", "gamma"]) + } + + // MARK: - Timeout + + /// An operation that hangs longer than its timeout must throw `.operationTimedOut`. + func testTimeoutFiresAndThrowsError() async { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + + do { + let _: Void = try await queue.enqueue(timeout: 0.05) { + // Hang for longer than the timeout + try await Task.sleep(nanoseconds: 10_000_000_000) // 10s + } + XCTFail("Expected operationTimedOut error") + } catch let error as BleError { + XCTAssertEqual(error.code, .operationTimedOut) + } catch { + XCTFail("Expected BleError but got: \(error)") + } + } + + /// Default timeout is used when no per-operation timeout is specified. + func testDefaultTimeoutIsApplied() async { + let queue = GATTOperationQueue(defaultTimeout: 0.05) // 50ms default + + do { + let _: Void = try await queue.enqueue { + try await Task.sleep(nanoseconds: 10_000_000_000) // 10s + } + XCTFail("Expected operationTimedOut error") + } catch let error as BleError { + XCTAssertEqual(error.code, .operationTimedOut) + } catch { + XCTFail("Expected BleError but got: \(error)") + } + } + + /// A fast operation must succeed even when a timeout is set. + func testFastOperationSucceedsWithTimeout() async throws { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + + let result: String = try await queue.enqueue(timeout: 2.0) { + "done" + } + XCTAssertEqual(result, "done") + } + + // MARK: - Cancel before execution + + /// Cancelling a transaction ID before calling enqueue must cause the enqueue + /// call to throw `.operationCancelled` immediately. + func testCancelBeforeEnqueueThrowsOperationCancelled() async { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + let txId = "pre-cancel-tx" + + // Mark cancelled before we enqueue + await queue.cancelTransaction(txId) + + do { + let _: Void = try await queue.enqueue(transactionId: txId) { + XCTFail("Operation body must not execute when pre-cancelled") + } + XCTFail("Expected operationCancelled error") + } catch let error as BleError { + XCTAssertEqual(error.code, .operationCancelled) + XCTAssertTrue(error.message.contains(txId)) + } catch { + XCTFail("Expected BleError but got: \(error)") + } + } + + // MARK: - Cancel between enqueue and execution + + /// Cancelling a transaction while a previous operation is still executing + /// must throw `.operationCancelled` when the queued operation finally starts. + func testCancelBetweenEnqueueAndExecutionThrowsOperationCancelled() async { + // Use a very short default timeout so the test doesn't hang if something goes wrong + let queue = GATTOperationQueue(defaultTimeout: 2.0) + + let firstStarted = AsyncSemaphore() + let cancelIssued = AsyncSemaphore() + let txId = "mid-queue-tx" + + // First operation blocks until we signal it + let firstTask = Task { + try await queue.enqueue { + await firstStarted.signal() + // Wait until the test has issued the cancel + await cancelIssued.wait() + try await Task.sleep(nanoseconds: 10_000_000) // 10ms — complete quickly + } + } + + // Wait for first op to actually start executing + await firstStarted.wait() + + // Enqueue second operation (it will be pending behind the first) + let secondTask = Task { + try await queue.enqueue(transactionId: txId) { + XCTFail("Cancelled operation body must not execute") + } + } + + // Cancel the second transaction while it is waiting in the queue + await queue.cancelTransaction(txId) + + // Let the first operation finish + await cancelIssued.signal() + + do { + try await firstTask.value + } catch { + XCTFail("First operation should not throw: \(error)") + } + + do { + try await secondTask.value + XCTFail("Expected operationCancelled for second task") + } catch let error as BleError { + XCTAssertEqual(error.code, .operationCancelled) + } catch { + XCTFail("Expected BleError but got: \(error)") + } + } + + // MARK: - isCancelled + + func testIsCancelledReturnsTrueAfterCancel() async { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + await queue.cancelTransaction("my-tx") + let cancelled = await queue.isCancelled("my-tx") + XCTAssertTrue(cancelled) + } + + func testIsCancelledReturnsFalseForUnknownTransaction() async { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + let cancelled = await queue.isCancelled("never-seen") + XCTAssertFalse(cancelled) + } + + // MARK: - Operations without transaction ID are not cancelled + + func testOperationWithoutTransactionIdIsNotAffectedByCancel() async throws { + let queue = GATTOperationQueue(defaultTimeout: 5.0) + // Cancel a transaction id that was never registered + await queue.cancelTransaction("unrelated-tx") + + let result: Int = try await queue.enqueue { + 42 + } + XCTAssertEqual(result, 42) + } +} + +// MARK: - AsyncSemaphore (test helper) + +/// Minimal async semaphore used to synchronise test tasks without blocking threads. +private actor AsyncSemaphore { + private var count = 0 + private var waiters: [CheckedContinuation] = [] + + func signal() { + if waiters.isEmpty { + count += 1 + } else { + let waiter = waiters.removeFirst() + waiter.resume() + } + } + + func wait() async { + if count > 0 { + count -= 1 + return + } + await withCheckedContinuation { (continuation: CheckedContinuation) in + waiters.append(continuation) + } + } +} diff --git a/ios/Tests/README.md b/ios/Tests/README.md new file mode 100644 index 000000000..5ff60b743 --- /dev/null +++ b/ios/Tests/README.md @@ -0,0 +1,120 @@ +# iOS Unit Tests + +This directory holds XCTest unit tests for the `react-native-ble-plx` iOS +TurboModule. Tests run on the iOS Simulator with no physical Bluetooth +hardware required thanks to Nordic Semiconductor's `CoreBluetoothMock` library. + +## Setup + +### Add CoreBluetoothMock via Swift Package Manager + +In Xcode, add the package dependency: +``` +https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock +``` +Version: `>= 0.18.0` + +Add it as a test-only dependency to the `react-native-ble-plxTests` target. + +### Create the Xcode test target + +In Xcode: File → New → Target → Unit Testing Bundle. +Name it `react-native-ble-plxTests`. Link it against the module's source +files (not the full app). + +## Test files to create + +### `BlePlxModuleTests.swift` + +Test the `BlePlxModule` TurboModule using `CBMCentralManagerMock`: + +```swift +import XCTest +import CoreBluetoothMock +@testable import react_native_ble_plx + +final class BlePlxModuleTests: XCTestCase { + // ... +} +``` + +Key test cases: + +- `testCreateClient_powersOnManager()` — after `createClient()`, the mock + central manager is in `.poweredOn` state and `onStateChange` fires with + `"PoweredOn"`. +- `testStartDeviceScan_callsScanAPI()` — `startDeviceScan(nil, nil)` calls + `centralManager.scanForPeripherals(withServices:options:)` with the + correct arguments. +- `testStopDeviceScan_stopsScanning()` — `stopDeviceScan()` calls + `centralManager.stopScan()`. +- `testConnectToDevice_resolvesWithDeviceInfo()` — mock a peripheral + discovery followed by a connection; verify the resolved `DeviceInfo` has + the correct fields. +- `testMonitorCharacteristic_receivesNotification()` — configure a mock + peripheral characteristic to send notifications and verify the + `onCharacteristicValueUpdate` event fires with base64-encoded data. +- `testCancelTransaction_stopsNotifications()` — after `cancelTransaction()`, + no further `onCharacteristicValueUpdate` events should arrive. +- `testUnexpectedDisconnect_firesOnConnectionStateChange()` — simulate a link + loss via `CBMCentralManagerMock` and verify the error event contains the + correct `errorCode`. + +### `BleErrorTests.swift` + +Test the iOS → `BleErrorCode` mapping: + +- `CBError.connectionTimeout` → `BleErrorCode.connectionTimeout` +- `CBATTError.insufficientAuthentication` → `BleErrorCode.bluetoothUnauthorized` +- `CBError.peripheralDisconnected` → `BleErrorCode.deviceDisconnected` + +### `Base64Tests.swift` + +Verify that characteristic value encoding round-trips correctly between +`Data` and base64 `String` for edge cases: +- Empty data → `""` +- Single zero byte → `"AA=="` +- Binary data with all byte values 0–255 + +## Running + +From the repository root: +```sh +xcodebuild test \ + -workspace ios/BlePlxExample.xcworkspace \ + -scheme react-native-ble-plxTests \ + -destination 'platform=iOS Simulator,name=iPhone 16' +``` + +Or within Xcode: select the `react-native-ble-plxTests` scheme and press +`Cmd+U`. + +## CoreBluetoothMock quick reference + +```swift +// Set up a mock central manager in poweredOn state +CBMCentralManagerMock.simulatePowerOn() + +// Register a mock peripheral +let mockPeripheral = CBMPeripheralSpec.simulatePeripheral( + identifier: UUID(), + proximity: .near +) +.advertising( + advertisementData: [ + CBAdvertisementDataLocalNameKey: "BlePlxTest", + CBAdvertisementDataServiceUUIDsKey: [CBUUID(string: "180D")] + ], + withInterval: 0.1 +) +.connectable( + name: "BlePlxTest", + services: [ /* CBMServiceMock instances */ ] +) +.build() + +CBMCentralManagerMock.simulatePeripherals([mockPeripheral]) +``` + +See the [CoreBluetoothMock documentation](https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock) +for the full API. diff --git a/ios/react_native_ble_plx.h b/ios/react_native_ble_plx.h new file mode 100644 index 000000000..6f2ed7ce8 --- /dev/null +++ b/ios/react_native_ble_plx.h @@ -0,0 +1,6 @@ +// +// react_native_ble_plx.h +// Umbrella header for react-native-ble-plx framework +// + +#import diff --git a/jest.config.js b/jest.config.js index bb1e79cb8..3f633ca98 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,5 @@ module.exports = { roots: ['/__tests__'], - preset: 'react-native' + preset: 'react-native', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] } diff --git a/lefthook.yml b/lefthook.yml index 9e3c91c59..513f5f5d0 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,7 +2,7 @@ pre-commit: parallel: true commands: lint: - files: git diff --name-only @{push} + files: git diff --name-only HEAD glob: '*.{js,ts,jsx,tsx}' run: yarn lint commit-msg: diff --git a/package.json b/package.json index af6c9028c..dc83885d1 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "react-native-ble-plx", - "version": "3.5.1", + "version": "4.0.0-alpha.0", "packageManager": "yarn@1.22.22", "description": "React Native Bluetooth Low Energy library", - "main": "src/index", - "module": "src/index", - "types": "src/index.d.ts", + "main": "lib/commonjs/index", + "module": "lib/module/index", + "types": "lib/typescript/src/index.d.ts", "react-native": "src/index", - "source": "src/index", + "source": "src/index.ts", "files": [ "src", "lib", @@ -35,9 +35,8 @@ "test": "yarn test:package ; yarn test:example", "test:package": "jest --config jest.config.js --passWithNoTests", "test:example": "jest --config example/jest.config.js --passWithNoTests", - "docs": "documentation build src/index.js -o docs --config documentation.yml -f html", "typecheck": "tsc --noEmit", - "lint": "flow && eslint \"**/*.{js,ts,tsx}\" && documentation lint index.js && yarn typecheck", + "lint": "eslint \"**/*.{js,ts,tsx}\" && yarn typecheck", "prepack": "bob build", "release": "release-it", "example": "yarn --cwd example", @@ -78,7 +77,6 @@ "devDependencies": { "@babel/cli": "^7.25.9", "@babel/core": "^7.25.2", - "@babel/preset-flow": "^7.25.9", "@commitlint/config-conventional": "^17.0.2", "@evilmartians/lefthook": "^1.5.0", "@react-native-community/eslint-config": "^3.0.2", @@ -88,26 +86,20 @@ "@release-it/conventional-changelog": "^5.0.0", "@types/jest": "^29.5.5", "@types/react": "^18.2.44", - "@types/react-native": "0.70.0", "@types/react-native-base64": "^0.2.2", "@typescript-eslint/parser": "^5.62.0", - "babel-plugin-syntax-hermes-parser": "^0.15.1", "commitlint": "^17.0.2", "del-cli": "^5.1.0", - "documentation": "12.3.0", "eslint": "^8.51.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-ft-flow": "^2.0.1", + "eslint-plugin-ft-flow": "^3.0.11", "eslint-plugin-import": "^2.28.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react-refresh": "^0.4.3", "expo-module-scripts": "^3.1.0", - "flow-bin": "^0.259.1", - "hermes-eslint": "^0.15.1", "jest": "^29.7.0", "pod-install": "^0.1.0", "prettier": "^3.0.3", @@ -127,7 +119,7 @@ }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": ">= 0.82.0" }, "engines": { "node": ">= 18.0.0" @@ -163,6 +155,21 @@ } } }, + "codegenConfig": { + "name": "NativeBlePlxSpec", + "type": "modules", + "jsSrcsDir": "src/specs", + "android": { + "javaPackageName": "com.bleplx" + }, + "ios": { + "modules": { + "NativeBlePlx": { + "className": "BlePlx" + } + } + } + }, "react-native-builder-bob": { "source": "src", "output": "lib", diff --git a/plugin/src/withBLE.ts b/plugin/src/withBLE.ts index 8a614e1ac..59585425e 100644 --- a/plugin/src/withBLE.ts +++ b/plugin/src/withBLE.ts @@ -19,7 +19,7 @@ const withBLE: ConfigPlugin< > = (config, props = {}) => { const _props = props || {} const isBackgroundEnabled = _props.isBackgroundEnabled ?? false - const neverForLocation = _props.neverForLocation ?? false + const neverForLocation = _props.neverForLocation ?? true if ('bluetoothPeripheralPermission' in _props) { WarningAggregator.addWarningIOS( diff --git a/plugin/tsconfig.tsbuildinfo b/plugin/tsconfig.tsbuildinfo new file mode 100644 index 000000000..5802b7d17 --- /dev/null +++ b/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/withble.ts","./src/withbleandroidmanifest.ts","./src/withblebackgroundmodes.ts","./src/withbluetoothpermissions.ts"],"version":"5.7.2"} \ No newline at end of file diff --git a/react-native-ble-plx.podspec b/react-native-ble-plx.podspec index c33512ec6..dc6ca93d2 100644 --- a/react-native-ble-plx.podspec +++ b/react-native-ble-plx.podspec @@ -11,33 +11,19 @@ Pod::Spec.new do |s| s.license = package["license"] s.authors = package["author"] - s.platforms = { :ios => "11.0" } + s.platforms = { :ios => "17.0" } s.source = { :git => "https://github.com/dotintent/react-native-ble-plx.git", :tag => "#{s.version}" } - s.source_files = "ios/**/*.{h,m,mm}" - s.dependency "MultiplatformBleAdapter", "0.2.0" - s.compiler_flags = "-DMULTIPLATFORM_BLE_ADAPTER -fmodules -fcxx-modules" + s.source_files = "ios/**/*.{h,m,mm,swift}" + s.exclude_files = ["ios/BlePlx-Bridging-Header.h", "ios/Tests/**/*", "ios/BlePlx.xcodeproj/**/*"] + s.swift_version = "5.9" - # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. - # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. - if respond_to?(:install_modules_dependencies, true) - install_modules_dependencies(s) - else - s.dependency "React-Core" + s.frameworks = "CoreBluetooth" - # Don't install the dependencies when we run `pod install` in the old architecture. - if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then - s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1 -DMULTIPLATFORM_BLE_ADAPTER -fmodules -fcxx-modules" - s.pod_target_xcconfig = { - "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", - "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" - } - s.dependency "React-Codegen" - s.dependency "RCT-Folly" - s.dependency "RCTRequired" - s.dependency "RCTTypeSafety" - s.dependency "ReactCommon/turbomodule/core" - end - end + s.pod_target_xcconfig = { + "DEFINES_MODULE" => "YES", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + install_modules_dependencies(s) end \ No newline at end of file diff --git a/src/BleError.js b/src/BleError.js deleted file mode 100644 index 73b973c4d..000000000 --- a/src/BleError.js +++ /dev/null @@ -1,555 +0,0 @@ -// @flow -import { fillStringWithArguments } from './Utils' -import type { BleErrorCodeMessageMapping } from './TypeDefinition' - -/** - * BleError is an error class which is guaranteed to be thrown by all functions of this - * library. It contains additional properties which help to identify problems in - * platform independent way. - */ -export class BleError extends Error { - /** - * Platform independent error code. Possible values are defined in {@link BleErrorCode}. - */ - errorCode: $Values - /** - * Platform independent error code related to ATT errors. - */ - attErrorCode: ?$Values - /** - * iOS specific error code (if not an ATT error). - */ - iosErrorCode: ?$Values - /** - * Android specific error code (if not an ATT error). - */ - androidErrorCode: ?$Values - /** - * Platform specific error message. - */ - reason: ?string - - constructor(nativeBleError: NativeBleError | string, errorMessageMapping: BleErrorCodeMessageMapping) { - super() - this.message = errorMessageMapping[BleErrorCode.UnknownError] - if (typeof nativeBleError === 'string') { - this.errorCode = BleErrorCode.UnknownError - this.attErrorCode = null - this.iosErrorCode = null - this.androidErrorCode = null - this.reason = nativeBleError - } else { - const message = errorMessageMapping[nativeBleError.errorCode] - if (message) { - this.message = fillStringWithArguments(message, nativeBleError) - } - this.errorCode = nativeBleError.errorCode - this.attErrorCode = nativeBleError.attErrorCode - this.iosErrorCode = nativeBleError.iosErrorCode - this.androidErrorCode = nativeBleError.androidErrorCode - this.reason = nativeBleError.reason - } - this.name = 'BleError' - } -} - -export function parseBleError(errorMessage: string, errorMessageMapping: BleErrorCodeMessageMapping): BleError { - let bleError: BleError - const errorMapping = errorMessageMapping ? errorMessageMapping : BleErrorCodeMessage - try { - const nativeBleError = JSON.parse(errorMessage) - bleError = new BleError(nativeBleError, errorMapping) - } catch (parseError) { - bleError = new BleError(errorMessage, errorMapping) - } - return bleError -} - -/** - * Platform independent error code map adjusted to this library's use cases. - */ -export const BleErrorCode = { - // Implementation specific errors ------------------------------------------------------------------------------------ - /** - * This error can be thrown when unexpected error occurred and in most cases it is related to implementation bug. - * Original message is available in {@link #bleerrorreason|reason} property. - */ - UnknownError: 0, - /** - * Current promise failed to finish due to BleManager shutdown. It means that user called - * {@link #blemanagerdestroy|manager.destroy()} function before all operations completed. - */ - BluetoothManagerDestroyed: 1, - /** - * Promise was explicitly cancelled by a user with {@link #blemanagercanceltransaction|manager.cancelTransaction()} - * function call. - */ - OperationCancelled: 2, - /** - * Operation timed out and was cancelled. - */ - OperationTimedOut: 3, - /** - * Native module couldn't start operation due to internal state, which doesn't allow to do that. - */ - OperationStartFailed: 4, - /** - * Invalid UUIDs or IDs were passed to API call. - */ - InvalidIdentifiers: 5, - - // Bluetooth global states ------------------------------------------------------------------------------------------- - /** - * Bluetooth is not supported for this particular device. It may happen on iOS simulator for example. - */ - BluetoothUnsupported: 100, - /** - * There are no granted permissions which allow to use BLE functionality. On Android it may require - * android.permission.ACCESS_COARSE_LOCATION permission or android.permission.ACCESS_FINE_LOCATION permission. - */ - BluetoothUnauthorized: 101, - /** - * BLE is powered off on device. All previous operations and their state is invalidated. - */ - BluetoothPoweredOff: 102, - /** - * BLE stack is in unspecified state. - */ - BluetoothInUnknownState: 103, - /** - * BLE stack is resetting. - */ - BluetoothResetting: 104, - /** - * BLE state change failed. - */ - BluetoothStateChangeFailed: 105, - - // Peripheral errors. ------------------------------------------------------------------------------------------------ - /** - * Couldn't connect to specified device. - */ - DeviceConnectionFailed: 200, - /** - * Device was disconnected. - */ - DeviceDisconnected: 201, - /** - * Couldn't read RSSI from device. - */ - DeviceRSSIReadFailed: 202, - /** - * Device is already connected. It is not allowed to connect twice to the same device. - */ - DeviceAlreadyConnected: 203, - /** - * Couldn't find device with specified ID. - */ - DeviceNotFound: 204, - /** - * Operation failed because device has to be connected to perform it. - */ - DeviceNotConnected: 205, - /** - * Device could not change MTU value. - */ - DeviceMTUChangeFailed: 206, - - // Services ---------------------------------------------------------------------------------------------------------- - /** - * Couldn't discover services for specified device. - */ - ServicesDiscoveryFailed: 300, - /** - * Couldn't discover included services for specified service. - */ - IncludedServicesDiscoveryFailed: 301, - /** - * Service with specified ID or UUID couldn't be found. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - ServiceNotFound: 302, - /** - * Services were not discovered. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - ServicesNotDiscovered: 303, - - // Characteristics --------------------------------------------------------------------------------------------------- - /** - * Couldn't discover characteristics for specified service. - */ - CharacteristicsDiscoveryFailed: 400, - /** - * Couldn't write to specified characteristic. Make sure that - * {@link #characteristiciswritablewithresponse|characteristic.isWritableWithResponse} - * or {@link #characteristiciswritablewithoutresponse|characteristic.isWritableWithoutResponse} is set to true. - */ - CharacteristicWriteFailed: 401, - /** - * Couldn't read from specified characteristic. Make sure that - * {@link #characteristicisreadable|characteristic.isReadable} is set to true. - */ - CharacteristicReadFailed: 402, - /** - * Couldn't set notification or indication for specified characteristic. Make sure that - * {@link #characteristicisnotifiable|characteristic.isNotifiable} or - * {@link #characteristicisindicatable|characteristic.isIndicatable} is set to true. - */ - CharacteristicNotifyChangeFailed: 403, - /** - * Characteristic with specified ID or UUID couldn't be found. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - CharacteristicNotFound: 404, - /** - * Characteristic were not discovered for specified service. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - CharacteristicsNotDiscovered: 405, - /** - * Invalid Base64 format was passed to characteristic API function call. - */ - CharacteristicInvalidDataFormat: 406, - - // Characteristics --------------------------------------------------------------------------------------------------- - /** - * Couldn't discover descriptor for specified characteristic. - */ - DescriptorsDiscoveryFailed: 500, - /** - * Couldn't write to specified descriptor. - */ - DescriptorWriteFailed: 501, - /** - * Couldn't read from specified descriptor. - */ - DescriptorReadFailed: 502, - /** - * Couldn't find specified descriptor. - */ - DescriptorNotFound: 503, - /** - * Descriptors are not discovered for specified characteristic. - */ - DescriptorsNotDiscovered: 504, - /** - * Invalid Base64 format was passed to descriptor API function call. - */ - DescriptorInvalidDataFormat: 505, - /** - * Issued a write to a descriptor, which is handled by OS. - */ - DescriptorWriteNotAllowed: 506, - - // Scanning errors --------------------------------------------------------------------------------------------------- - /** - * Cannot start scanning operation. - */ - ScanStartFailed: 600, - /** - * Location services are disabled. - */ - LocationServicesDisabled: 601 -} - -/** - * Mapping of error codes to error messages - * @name BleErrorCodeMessage - */ -export const BleErrorCodeMessage: BleErrorCodeMessageMapping = { - // Implementation specific errors - [BleErrorCode.UnknownError]: 'Unknown error occurred. This is probably a bug! Check reason property.', - [BleErrorCode.BluetoothManagerDestroyed]: 'BleManager was destroyed', - [BleErrorCode.OperationCancelled]: 'Operation was cancelled', - [BleErrorCode.OperationTimedOut]: 'Operation timed out', - [BleErrorCode.OperationStartFailed]: 'Operation was rejected', - [BleErrorCode.InvalidIdentifiers]: 'Invalid UUIDs or IDs were passed: {internalMessage}', - - // Bluetooth global states - [BleErrorCode.BluetoothUnsupported]: 'BluetoothLE is unsupported on this device', - [BleErrorCode.BluetoothUnauthorized]: 'Device is not authorized to use BluetoothLE', - [BleErrorCode.BluetoothPoweredOff]: 'BluetoothLE is powered off', - [BleErrorCode.BluetoothInUnknownState]: 'BluetoothLE is in unknown state', - [BleErrorCode.BluetoothResetting]: 'BluetoothLE is resetting', - [BleErrorCode.BluetoothStateChangeFailed]: 'Bluetooth state change failed', - - // Peripheral errors. - [BleErrorCode.DeviceConnectionFailed]: 'Device {deviceID} connection failed', - [BleErrorCode.DeviceDisconnected]: 'Device {deviceID} was disconnected', - [BleErrorCode.DeviceRSSIReadFailed]: 'RSSI read failed for device {deviceID}', - [BleErrorCode.DeviceAlreadyConnected]: 'Device {deviceID} is already connected', - [BleErrorCode.DeviceNotFound]: 'Device {deviceID} not found', - [BleErrorCode.DeviceNotConnected]: 'Device {deviceID} is not connected', - [BleErrorCode.DeviceMTUChangeFailed]: 'Device {deviceID} could not change MTU size', - - // Services - [BleErrorCode.ServicesDiscoveryFailed]: 'Services discovery failed for device {deviceID}', - [BleErrorCode.IncludedServicesDiscoveryFailed]: - 'Included services discovery failed for device {deviceID} and service: {serviceUUID}', - [BleErrorCode.ServiceNotFound]: 'Service {serviceUUID} for device {deviceID} not found', - [BleErrorCode.ServicesNotDiscovered]: 'Services not discovered for device {deviceID}', - - // Characteristics - [BleErrorCode.CharacteristicsDiscoveryFailed]: - 'Characteristic discovery failed for device {deviceID} and service {serviceUUID}', - [BleErrorCode.CharacteristicWriteFailed]: - 'Characteristic {characteristicUUID} write failed for device {deviceID} and service {serviceUUID}', - [BleErrorCode.CharacteristicReadFailed]: - 'Characteristic {characteristicUUID} read failed for device {deviceID} and service {serviceUUID}', - [BleErrorCode.CharacteristicNotifyChangeFailed]: - 'Characteristic {characteristicUUID} notify change failed for device {deviceID} and service {serviceUUID}', - [BleErrorCode.CharacteristicNotFound]: 'Characteristic {characteristicUUID} not found', - [BleErrorCode.CharacteristicsNotDiscovered]: - 'Characteristics not discovered for device {deviceID} and service {serviceUUID}', - [BleErrorCode.CharacteristicInvalidDataFormat]: - 'Cannot write to characteristic {characteristicUUID} with invalid data format: {internalMessage}', - - // Descriptors - [BleErrorCode.DescriptorsDiscoveryFailed]: - 'Descriptor {descriptorUUID} discovery failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}', - [BleErrorCode.DescriptorWriteFailed]: - 'Descriptor {descriptorUUID} write failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}', - [BleErrorCode.DescriptorReadFailed]: - 'Descriptor {descriptorUUID} read failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}', - [BleErrorCode.DescriptorNotFound]: 'Descriptor {descriptorUUID} not found', - [BleErrorCode.DescriptorsNotDiscovered]: - 'Descriptors not discovered for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}', - [BleErrorCode.DescriptorInvalidDataFormat]: - 'Cannot write to descriptor {descriptorUUID} with invalid data format: {internalMessage}', - [BleErrorCode.DescriptorWriteNotAllowed]: - "Cannot write to descriptor {descriptorUUID}. It's not allowed by iOS and therefore forbidden on Android as well.", - - // Scanning - [BleErrorCode.ScanStartFailed]: 'Cannot start scanning operation', - [BleErrorCode.LocationServicesDisabled]: 'Location services are disabled' -} - -/** - * Error codes for ATT errors. - * @name BleATTErrorCode - */ -export const BleATTErrorCode = { - /** - * The ATT command or request successfully completed. - */ - Success: 0, - /** - * The attribute handle is invalid on this device. - */ - InvalidHandle: 1, - /** - * The attribute’s value cannot be read. - */ - ReadNotPermitted: 2, - /** - * The attribute’s value cannot be written. - */ - WriteNotPermitted: 3, - /** - * The attribute Protocol Data Unit (PDU) or “message” is invalid. - */ - InvalidPdu: 4, - /** - * The attribute requires authentication before its value can be read or written. - */ - InsufficientAuthentication: 5, - /** - * The attribute server does not support the request received by the client. - */ - RequestNotSupported: 6, - /** - * The specified offset value was past the end of the attribute’s value. - */ - InvalidOffset: 7, - /** - * The attribute requires authorization before its value can be read or written. - */ - InsufficientAuthorization: 8, - /** - * The prepare queue is full, because too many prepare write requests have been queued. - */ - PrepareQueueFull: 9, - /** - * The attribute is not found within the specified attribute handle range. - */ - AttributeNotFound: 10, - /** - * The attribute cannot be read or written using the ATT read blob request. - */ - AttributeNotLong: 11, - /** - * The encryption key size used for encrypting this link is insufficient. - */ - InsufficientEncryptionKeySize: 12, - /** - * The length of the attribute’s value is invalid for the intended operation. - */ - InvalidAttributeValueLength: 13, - /** - * The ATT request has encountered an unlikely error and therefore could not be completed. - */ - UnlikelyError: 14, - /** - * The attribute requires encryption before its value can be read or written. - */ - InsufficientEncryption: 15, - /** - * The attribute type is not a supported grouping attribute as defined by a higher-layer specification. - */ - UnsupportedGroupType: 16, - /** - * Resources are insufficient to complete the ATT request. - */ - InsufficientResources: 17 - - // Values 0x012 – 0x7F are reserved for future use. -} - -/** - * iOS specific error codes. - * @name BleIOSErrorCode - */ -export const BleIOSErrorCode = { - /** - * An unknown error occurred. - */ - Unknown: 0, - /** - * The specified parameters are invalid. - */ - InvalidParameters: 1, - /** - * The specified attribute handle is invalid. - */ - InvalidHandle: 2, - /** - * The device is not currently connected. - */ - NotConnected: 3, - /** - * The device has run out of space to complete the intended operation. - */ - OutOfSpace: 4, - /** - * The operation is canceled. - */ - OperationCancelled: 5, - /** - * The connection timed out. - */ - ConnectionTimeout: 6, - /** - * The peripheral disconnected. - */ - PeripheralDisconnected: 7, - /** - * The specified UUID is not permitted. - */ - UuidNotAllowed: 8, - /** - * The peripheral is already advertising. - */ - AlreadyAdvertising: 9, - /** - * The connection failed. - */ - ConnectionFailed: 10, - /** - * The device already has the maximum number of connections. - */ - ConnectionLimitReached: 11, - /** - * Unknown device. - */ - UnknownDevice: 12 -} - -/** - * Android specific error codes. - * @name BleAndroidErrorCode - */ -export const BleAndroidErrorCode = { - /** - * The device has insufficient resources to complete the intended operation. - */ - NoResources: 0x80, - /** - * Internal error occurred which may happen due to implementation error in BLE stack. - */ - InternalError: 0x81, - /** - * BLE stack implementation entered illegal state and operation couldn't complete. - */ - WrongState: 0x82, - /** - * BLE stack didn't allocate sufficient memory buffer for internal caches. - */ - DbFull: 0x83, - /** - * Maximum number of pending operations was exceeded. - */ - Busy: 0x84, - /** - * Generic BLE stack error. - */ - Error: 0x85, - /** - * Command is already queued up in GATT. - */ - CmdStarted: 0x86, - /** - * Illegal parameter was passed to internal BLE stack function. - */ - IllegalParameter: 0x87, - /** - * Operation is pending. - */ - Pending: 0x88, - /** - * Authorization failed before performing read or write operation. - */ - AuthFail: 0x89, - /** - * More cache entries were loaded then expected. - */ - More: 0x8a, - /** - * Invalid configuration - */ - InvalidCfg: 0x8b, - /** - * GATT service already started. - */ - ServiceStarted: 0x8c, - /** - * GATT link is encrypted but prone to man in the middle attacks. - */ - EncrypedNoMitm: 0x8d, - /** - * GATT link is not encrypted. - */ - NotEncrypted: 0x8e, - /** - * ATT command was sent but channel is congested. - */ - Congested: 0x8f -} - -export interface NativeBleError { - errorCode: $Values; - attErrorCode: ?$Values; - iosErrorCode: ?$Values; - androidErrorCode: ?$Values; - reason: ?string; - - deviceID?: string; - serviceUUID?: string; - characteristicUUID?: string; - descriptorUUID?: string; - internalMessage?: string; -} diff --git a/src/BleError.ts b/src/BleError.ts new file mode 100644 index 000000000..148354469 --- /dev/null +++ b/src/BleError.ts @@ -0,0 +1,78 @@ +// src/BleError.ts — Unified cross-platform error model + +export const BleErrorCode = { + DeviceNotFound: 0, + DeviceDisconnected: 1, + ConnectionFailed: 2, + ConnectionTimeout: 3, + OperationCancelled: 100, + OperationTimeout: 101, + OperationNotSupported: 102, + OperationInProgress: 103, + CharacteristicNotFound: 200, + ServiceNotFound: 201, + DescriptorNotFound: 202, + CharacteristicWriteFailed: 203, + CharacteristicReadFailed: 204, + MTUNegotiationFailed: 205, + BluetoothUnauthorized: 300, + BluetoothPoweredOff: 301, + LocationPermissionDenied: 302, + ScanPermissionDenied: 303, + ConnectPermissionDenied: 304, + ManagerNotInitialized: 400, + ManagerDestroyed: 401, + BondingFailed: 500, + BondLost: 501, + PairingRejected: 502, + L2CAPChannelFailed: 600, + L2CAPChannelClosed: 601, + PhyNegotiationFailed: 700, + ScanFailed: 800, + ScanThrottled: 801, + UnknownError: 999 +} as const +export type BleErrorCode = (typeof BleErrorCode)[keyof typeof BleErrorCode] + +export class BleError extends Error { + readonly code: BleErrorCode + readonly isRetryable: boolean + readonly deviceId?: string + readonly serviceUUID?: string + readonly characteristicUUID?: string + readonly operation?: string + readonly platform: 'android' | 'ios' + readonly nativeDomain?: string + readonly nativeCode?: number + readonly gattStatus?: number + readonly attErrorCode?: number + + constructor(errorInfo: { + code: number + message: string + isRetryable: boolean + platform: string + deviceId?: string | null + serviceUuid?: string | null + characteristicUuid?: string | null + operation?: string | null + nativeDomain?: string | null + nativeCode?: number | null + gattStatus?: number | null + attErrorCode?: number | null + }) { + super(errorInfo.message) + this.name = 'BleError' + this.code = errorInfo.code as BleErrorCode + this.isRetryable = errorInfo.isRetryable + this.platform = errorInfo.platform as 'android' | 'ios' + this.deviceId = errorInfo.deviceId ?? undefined + this.serviceUUID = errorInfo.serviceUuid ?? undefined + this.characteristicUUID = errorInfo.characteristicUuid ?? undefined + this.operation = errorInfo.operation ?? undefined + this.nativeDomain = errorInfo.nativeDomain ?? undefined + this.nativeCode = errorInfo.nativeCode ?? undefined + this.gattStatus = errorInfo.gattStatus ?? undefined + this.attErrorCode = errorInfo.attErrorCode ?? undefined + } +} diff --git a/src/BleManager.js b/src/BleManager.js deleted file mode 100644 index 4e388873d..000000000 --- a/src/BleManager.js +++ /dev/null @@ -1,1320 +0,0 @@ -// @flow -'use strict' - -import { Device } from './Device' -import { Service } from './Service' -import { Characteristic } from './Characteristic' -import { Descriptor } from './Descriptor' -import { State, LogLevel, ConnectionPriority } from './TypeDefinition' -import { BleModule, EventEmitter } from './BleModule' -import { - parseBleError, - BleError, - BleErrorCode, - BleErrorCodeMessage, - BleATTErrorCode, - BleAndroidErrorCode, - BleIOSErrorCode -} from './BleError' -import type { NativeDevice, NativeCharacteristic, NativeDescriptor, NativeBleRestoredState } from './BleModule' -import type { - BleErrorCodeMessageMapping, - Subscription, - DeviceId, - Identifier, - UUID, - TransactionId, - CharacteristicSubscriptionType, - Base64, - ScanOptions, - ConnectionOptions, - BleManagerOptions -} from './TypeDefinition' -import { isIOS } from './Utils' -import { Platform } from 'react-native' - -const enableDisableDeprecatedMessage = - 'react-native-ble-plx: The enable and disable feature is no longer supported. In Android SDK 31+ there were major changes in permissions, which may cause problems with these functions, and in SDK 33+ they were completely removed.' - -/** - * - * BleManager is an entry point for react-native-ble-plx library. It provides all means to discover and work with - * {@link Device} instances. It should be initialized only once with `new` keyword and method - * {@link #blemanagerdestroy|destroy()} should be called on its instance when user wants to deallocate all resources. - * - * In case you want to properly support Background Mode, you should provide `restoreStateIdentifier` and - * `restoreStateFunction` in {@link BleManagerOptions}. - * - * @example - * const manager = new BleManager(); - * // ... work with BLE manager ... - * manager.destroy(); - */ -export class BleManager { - // Scan subscriptions - // $FlowIssue[missing-type-arg] - _scanEventSubscription: ?EventEmitter - // Listening to BleModule events - // $FlowIssue[missing-type-arg] - _eventEmitter: EventEmitter - // Unique identifier used to create internal transactionIds - _uniqueId: number - // Map of active promises with functions to forcibly cancel them - _activePromises: { [id: string]: (error: BleError) => void } - // Map of active subscriptions - _activeSubscriptions: { [id: string]: Subscription } - - // Map of error codes to error messages - _errorCodesToMessagesMapping: BleErrorCodeMessageMapping - - static sharedInstance: BleManager | null = null - - /** - * Creates an instance of {@link BleManager}. - * It will return already created instance if it was created before. - * If you want to create a new instance to for example use different options, you have to call {@link #blemanagerdestroy|destroy()} on the previous one. - */ - constructor(options: BleManagerOptions = {}) { - if (BleManager.sharedInstance !== null) { - // $FlowFixMe - Constructor returns shared instance for singleton pattern - return BleManager.sharedInstance - } - - this._eventEmitter = new EventEmitter(BleModule) - this._uniqueId = 0 - this._activePromises = {} - this._activeSubscriptions = {} - - const restoreStateFunction = options.restoreStateFunction - if (restoreStateFunction != null && options.restoreStateIdentifier != null) { - // $FlowIssue[prop-missing] - this._activeSubscriptions[this._nextUniqueID()] = this._eventEmitter.addListener( - BleModule.RestoreStateEvent, - (nativeRestoredState: NativeBleRestoredState) => { - if (nativeRestoredState == null) { - restoreStateFunction(null) - return - } - restoreStateFunction({ - connectedPeripherals: nativeRestoredState.connectedPeripherals.map( - nativeDevice => new Device(nativeDevice, this) - ) - }) - } - ) - } - - this._errorCodesToMessagesMapping = options.errorCodesToMessagesMapping - ? options.errorCodesToMessagesMapping - : BleErrorCodeMessage - - BleModule.createClient(options.restoreStateIdentifier || null) - BleManager.sharedInstance = this - } - - /** - * Destroys all promises which are in progress. - * @private - */ - _destroyPromises() { - const destroyedError = new BleError( - { - errorCode: BleErrorCode.BluetoothManagerDestroyed, - attErrorCode: (null: ?$Values), - iosErrorCode: (null: ?$Values), - androidErrorCode: (null: ?$Values), - reason: (null: ?string) - }, - this._errorCodesToMessagesMapping - ) - for (const id in this._activePromises) { - this._activePromises[id](destroyedError) - } - } - - /** - * Destroys all subscriptions. - * @private - */ - _destroySubscriptions() { - for (const id in this._activeSubscriptions) { - this._activeSubscriptions[id].remove() - } - } - - /** - * Destroys {@link BleManager} instance. A new instance needs to be created to continue working with - * this library. All operations which were in progress completes with - * @returns {Promise} Promise may return an error when the function cannot be called. - * {@link #bleerrorcodebluetoothmanagerdestroyed|BluetoothManagerDestroyed} error code. - */ - async destroy(): Promise { - const response = await this._callPromise(BleModule.destroyClient()) - - // Unsubscribe from any subscriptions - if (this._scanEventSubscription != null) { - this._scanEventSubscription.remove() - this._scanEventSubscription = null - } - this._destroySubscriptions() - - if (BleManager.sharedInstance) { - BleManager.sharedInstance = null - } - - // Destroy all promises - this._destroyPromises() - - return response - } - - /** - * Generates new unique identifier to be used internally. - * - * @returns {string} New identifier. - * @private - */ - _nextUniqueID(): string { - this._uniqueId += 1 - return this._uniqueId.toString() - } - - /** - * Calls promise and checks if it completed successfully - * - * @param {Promise} promise Promise to be called - * @returns {Promise} Value of called promise. - * @private - */ - async _callPromise(promise: Promise): Promise { - const id = this._nextUniqueID() - try { - const destroyPromise = new Promise((resolve, reject) => { - this._activePromises[id] = reject - }) - const value = await Promise.race([destroyPromise, promise]) - delete this._activePromises[id] - // $FlowIssue[incompatible-return] - return value - } catch (error) { - delete this._activePromises[id] - throw parseBleError(error.message, this._errorCodesToMessagesMapping) - } - } - - // Mark: Common ------------------------------------------------------------------------------------------------------ - - /** - * Sets new log level for native module's logging mechanism. - * @param {LogLevel} logLevel New log level to be set. - * @returns {Promise} Current log level. - */ - setLogLevel(logLevel: $Keys): Promise<$Keys | void> { - return this._callPromise(BleModule.setLogLevel(logLevel)) - } - - /** - * Get current log level for native module's logging mechanism. - * @returns {Promise} Current log level. - */ - logLevel(): Promise<$Keys> { - return this._callPromise(BleModule.logLevel()) - } - - /** - * Cancels pending transaction. - * - * Few operations such as monitoring characteristic's value changes can be cancelled by a user. Basically every API - * entry which accepts `transactionId` allows to call `cancelTransaction` function. When cancelled operation is a - * promise or a callback which registers errors, {@link #bleerror|BleError} with error code - * {@link #bleerrorcodeoperationcancelled|OperationCancelled} will be emitted in that case. Cancelling transaction - * which doesn't exist is ignored. - * - * @example - * const transactionId = 'monitor_battery'; - * - * // Monitor battery notifications - * manager.monitorCharacteristicForDevice( - * device.id, '180F', '2A19', - * (error, characteristic) => { - * // Handle battery level changes... - * }, transactionId); - * - * // Cancel after specified amount of time - * setTimeout(() => manager.cancelTransaction(transactionId), 2000); - * - * @param {TransactionId} transactionId Id of pending transactions. - * @returns {Promise} - */ - cancelTransaction(transactionId: TransactionId): Promise { - return this._callPromise(BleModule.cancelTransaction(transactionId)) - } - - // Mark: Monitoring state -------------------------------------------------------------------------------------------- - - /** - * Enable Bluetooth. This function blocks until BLE is in PoweredOn state. [Android only] - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise completes when state transition was successful. - */ - async enable(transactionId: ?TransactionId): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - await this._callPromise(BleModule.enable(transactionId)) - return this - } - - /** - * Deprecated - * Disable Bluetooth. This function blocks until BLE is in PoweredOff state. [Android only] - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise completes when state transition was successful. - */ - async disable(transactionId: ?TransactionId): Promise { - console.warn(enableDisableDeprecatedMessage) - if (!transactionId) { - transactionId = this._nextUniqueID() - } - await this._callPromise(BleModule.disable(transactionId)) - return this - } - - /** - * Current, global {@link State} of a {@link BleManager}. All APIs are working only when active state - * is "PoweredOn". - * - * @returns {Promise} Promise which emits current state of BleManager. - */ - state(): Promise<$Keys> { - return this._callPromise(BleModule.state()) - } - - /** - * Notifies about {@link State} changes of a {@link BleManager}. - * - * @example - * const subscription = this.manager.onStateChange((state) => { - * if (state === 'PoweredOn') { - * this.scanAndConnect(); - * subscription.remove(); - * } - * }, true); - * - * @param {function(newState: State)} listener Callback which emits state changes of BLE Manager. - * Look at {@link State} for possible values. - * @param {boolean} [emitCurrentState=false] If true, current state will be emitted as well. Defaults to false. - * - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - onStateChange(listener: (newState: $Keys) => void, emitCurrentState: boolean = false): Subscription { - const subscription: Subscription = this._eventEmitter.addListener(BleModule.StateChangeEvent, listener) - const id = this._nextUniqueID() - var wrappedSubscription: Subscription - - if (emitCurrentState) { - var cancelled = false - this._callPromise(this.state()).then( - currentState => { - if (!cancelled) { - listener(currentState) - } - }, - () => { - // Prevent unhandled promise rejection for internal state fetch. - } - ) - - wrappedSubscription = { - remove: () => { - if (this._activeSubscriptions[id] != null) { - cancelled = true - delete this._activeSubscriptions[id] - subscription.remove() - } - } - } - } else { - wrappedSubscription = { - remove: () => { - if (this._activeSubscriptions[id] != null) { - delete this._activeSubscriptions[id] - subscription.remove() - } - } - } - } - - this._activeSubscriptions[id] = wrappedSubscription - return wrappedSubscription - } - - // Mark: Scanning ---------------------------------------------------------------------------------------------------- - - /** - * Starts device scanning. When previous scan is in progress it will be stopped before executing this command. - * - * @param {?Array} UUIDs Array of strings containing {@link UUID}s of {@link Service}s which are registered in - * scanned {@link Device}. If `null` is passed, all available {@link Device}s will be scanned. - * @param {?ScanOptions} options Optional configuration for scanning operation. - * @param {function(error: ?BleError, scannedDevice: ?Device)} listener Function which will be called for every scanned - * @returns {Promise} Promise may return an error when the function cannot be called. - * {@link Device} (devices may be scanned multiple times). It's first argument is potential {@link Error} which is set - * to non `null` value when scanning failed. You have to start scanning process again if that happens. Second argument - * is a scanned {@link Device}. - * @returns {Promise} the promise may be rejected if the operation is impossible to perform. - */ - async startDeviceScan( - UUIDs: ?Array, - options: ?ScanOptions, - listener: (error: ?BleError, scannedDevice: ?Device) => Promise - ): Promise { - const scanListener = ([error, nativeDevice]: [?string, ?NativeDevice]) => { - listener( - error ? parseBleError(error, this._errorCodesToMessagesMapping) : null, - nativeDevice ? new Device(nativeDevice, this) : null - ) - } - // $FlowFixMe: Flow cannot deduce EmitterSubscription type. - this._scanEventSubscription = this._eventEmitter.addListener(BleModule.ScanEvent, scanListener) - - return this._callPromise(BleModule.startDeviceScan(UUIDs, options)) - } - - /** - * Stops {@link Device} scan if in progress. - * @returns {Promise} the promise may be rejected if the operation is impossible to perform. - */ - stopDeviceScan(): Promise { - if (this._scanEventSubscription != null) { - this._scanEventSubscription.remove() - this._scanEventSubscription = null - } - - return this._callPromise(BleModule.stopDeviceScan()) - } - - /** - * Request a connection parameter update. This functions may update connection parameters on Android API level 21 or - * above. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {ConnectionPriority} connectionPriority: Connection priority. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Connected device. - */ - async requestConnectionPriorityForDevice( - deviceIdentifier: DeviceId, - connectionPriority: $Values, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDevice = await this._callPromise( - BleModule.requestConnectionPriorityForDevice(deviceIdentifier, connectionPriority, transactionId) - ) - return new Device(nativeDevice, this) - } - - /** - * Reads RSSI for connected device. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Connected device with updated RSSI value. - */ - async readRSSIForDevice(deviceIdentifier: DeviceId, transactionId: ?TransactionId): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDevice = await this._callPromise(BleModule.readRSSIForDevice(deviceIdentifier, transactionId)) - return new Device(nativeDevice, this) - } - - /** - * Request new MTU value for this device. This function currently is not doing anything - * on iOS platform as MTU exchange is done automatically. Since Android 14, - * mtu management has been changed, more information can be found at the link: - * https://developer.android.com/about/versions/14/behavior-changes-all#mtu-set-to-517 - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {number} mtu New MTU to negotiate. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Device with updated MTU size. Default value is 23 (517 since Android 14).. - */ - async requestMTUForDevice(deviceIdentifier: DeviceId, mtu: number, transactionId: ?TransactionId): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDevice = await this._callPromise(BleModule.requestMTUForDevice(deviceIdentifier, mtu, transactionId)) - return new Device(nativeDevice, this) - } - - // Mark: Connection management --------------------------------------------------------------------------------------- - - /** - * Returns a list of known devices by their identifiers. - * @param {Array} deviceIdentifiers List of device identifiers. - * @returns {Promise>} List of known devices by their identifiers. - */ - async devices(deviceIdentifiers: Array): Promise> { - const nativeDevices = await this._callPromise(BleModule.devices(deviceIdentifiers)) - return nativeDevices.map((nativeDevice: NativeDevice) => { - return new Device(nativeDevice, this) - }) - } - - /** - * Returns a list of the peripherals (containing any of the specified services) currently connected to the system - * which have discovered services. Returned devices **may not be connected** to your application. Make sure to check - * if that's the case with function {@link #blemanagerisdeviceconnected|isDeviceConnected}. - * @param {Array} serviceUUIDs List of service UUIDs. Device must contain at least one of them to be listed. - * @returns {Promise>} List of known devices with discovered services as stated in the parameter. - */ - async connectedDevices(serviceUUIDs: Array): Promise> { - const nativeDevices = await this._callPromise(BleModule.connectedDevices(serviceUUIDs)) - return nativeDevices.map((nativeDevice: NativeDevice) => { - return new Device(nativeDevice, this) - }) - } - - // Mark: Connection management --------------------------------------------------------------------------------------- - - /** - * Connects to {@link Device} with provided ID. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {?ConnectionOptions} options Platform specific options for connection establishment. - * @returns {Promise} Connected {@link Device} object if successful. - */ - async connectToDevice(deviceIdentifier: DeviceId, options: ?ConnectionOptions): Promise { - if (Platform.OS === 'android' && (await this.isDeviceConnected(deviceIdentifier))) { - await this.cancelDeviceConnection(deviceIdentifier) - } - const nativeDevice = await this._callPromise(BleModule.connectToDevice(deviceIdentifier, options)) - return new Device(nativeDevice, this) - } - - /** - * Disconnects from {@link Device} if it's connected or cancels pending connection. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier to be closed. - * @returns {Promise} Returns closed {@link Device} when operation is successful. - */ - async cancelDeviceConnection(deviceIdentifier: DeviceId): Promise { - const nativeDevice = await this._callPromise(BleModule.cancelDeviceConnection(deviceIdentifier)) - return new Device(nativeDevice, this) - } - - /** - * Monitors if {@link Device} was disconnected due to any errors or connection problems. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier to be monitored. - * @param {function(error: ?BleError, device: Device)} listener - callback returning error as a reason of disconnection - * if available and {@link Device} object. If an error is null, that means the connection was terminated by - * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} call. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - onDeviceDisconnected(deviceIdentifier: DeviceId, listener: (error: ?BleError, device: Device) => void): Subscription { - const disconnectionListener = ([error, nativeDevice]: [?string, NativeDevice]) => { - if (deviceIdentifier !== nativeDevice.id) { - return - } - listener(error ? parseBleError(error, this._errorCodesToMessagesMapping) : null, new Device(nativeDevice, this)) - } - - const subscription: Subscription = this._eventEmitter.addListener( - BleModule.DisconnectionEvent, - disconnectionListener - ) - - const id = this._nextUniqueID() - const wrappedSubscription = { - remove: () => { - if (this._activeSubscriptions[id] != null) { - delete this._activeSubscriptions[id] - subscription.remove() - } - } - } - this._activeSubscriptions[id] = wrappedSubscription - return wrappedSubscription - } - - /** - * Check connection state of a {@link Device}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @returns {Promise} Promise which emits `true` if device is connected, and `false` otherwise. - */ - isDeviceConnected(deviceIdentifier: DeviceId): Promise { - return this._callPromise(BleModule.isDeviceConnected(deviceIdentifier)) - } - - // Mark: Discovery --------------------------------------------------------------------------------------------------- - - /** - * Discovers all {@link Service}s, {@link Characteristic}s and {@link Descriptor}s for {@link Device}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise which emits {@link Device} object if all available services and - * characteristics have been discovered. - */ - async discoverAllServicesAndCharacteristicsForDevice( - deviceIdentifier: DeviceId, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDevice = await this._callPromise( - BleModule.discoverAllServicesAndCharacteristicsForDevice(deviceIdentifier, transactionId) - ) - const services = await this._callPromise(BleModule.servicesForDevice(deviceIdentifier)) - const serviceUUIDs = (services || []).map(service => service.uuid) - - // $FlowFixMe - const device = { - ...nativeDevice, - serviceUUIDs - } - return new Device(device, this) - } - - // Mark: Service and characteristic getters -------------------------------------------------------------------------- - - /** - * List of discovered {@link Service}s for {@link Device}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @returns {Promise>} Promise which emits array of {@link Service} objects which are discovered for a - * {@link Device}. - */ - async servicesForDevice(deviceIdentifier: DeviceId): Promise> { - const services = await this._callPromise(BleModule.servicesForDevice(deviceIdentifier)) - return services.map(nativeService => { - return new Service(nativeService, this) - }) - } - - /** - * List of discovered {@link Characteristic}s for given {@link Device} and {@link Service}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @returns {Promise>} Promise which emits array of {@link Characteristic} objects which are - * discovered for a {@link Device} in specified {@link Service}. - */ - characteristicsForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID): Promise> { - return this._handleCharacteristics(BleModule.characteristicsForDevice(deviceIdentifier, serviceUUID)) - } - - /** - * List of discovered {@link Characteristic}s for unique {@link Service}. - * - * @param {Identifier} serviceIdentifier {@link Service} ID. - * @returns {Promise>} Promise which emits array of {@link Characteristic} objects which are - * discovered in unique {@link Service}. - * @private - */ - _characteristicsForService(serviceIdentifier: Identifier): Promise> { - return this._handleCharacteristics(BleModule.characteristicsForService(serviceIdentifier)) - } - - /** - * Common code for handling NativeCharacteristic fetches. - * - * @param {Promise>} characteristicsPromise Native characteristics. - * @returns {Promise>} Promise which emits array of {@link Characteristic} objects which are - * discovered in unique {@link Service}. - * @private - */ - async _handleCharacteristics( - characteristicsPromise: Promise> - ): Promise> { - const characteristics = await this._callPromise(characteristicsPromise) - return characteristics.map(nativeCharacteristic => { - return new Characteristic(nativeCharacteristic, this) - }) - } - - /** - * List of discovered {@link Descriptor}s for given {@link Device}, {@link Service} and {@link Characteristic}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for a {@link Device}, {@link Service} in specified {@link Characteristic}. - */ - descriptorsForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID - ): Promise> { - return this._handleDescriptors(BleModule.descriptorsForDevice(deviceIdentifier, serviceUUID, characteristicUUID)) - } - - /** - * List of discovered {@link Descriptor}s for given {@link Service} and {@link Characteristic}. - * - * @param {Identifier} serviceIdentifier {@link Service} identifier. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for a {@link Service} in specified {@link Characteristic}. - * @private - */ - _descriptorsForService(serviceIdentifier: Identifier, characteristicUUID: UUID): Promise> { - return this._handleDescriptors(BleModule.descriptorsForService(serviceIdentifier, characteristicUUID)) - } - - /** - * List of discovered {@link Descriptor}s for given {@link Characteristic}. - * - * @param {Identifier} characteristicIdentifier {@link Characteristic} identifier. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered in specified {@link Characteristic}. - * @private - */ - _descriptorsForCharacteristic(characteristicIdentifier: Identifier): Promise> { - return this._handleDescriptors(BleModule.descriptorsForCharacteristic(characteristicIdentifier)) - } - - /** - * Common code for handling NativeDescriptor fetches. - * @param {Promise>} descriptorsPromise Native descriptors. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered in unique {@link Characteristic}. - * @private - */ - async _handleDescriptors(descriptorsPromise: Promise>): Promise> { - const descriptors = await this._callPromise(descriptorsPromise) - return descriptors.map(nativeDescriptor => { - return new Descriptor(nativeDescriptor, this) - }) - } - - // Mark: Characteristics operations ---------------------------------------------------------------------------------- - - /** - * Read {@link Characteristic} value. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. - */ - async readCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.readCharacteristicForDevice(deviceIdentifier, serviceUUID, characteristicUUID, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Read {@link Characteristic} value. - * - * @param {Identifier} serviceIdentifier {@link Service} ID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. - * @private - */ - async _readCharacteristicForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.readCharacteristicForService(serviceIdentifier, characteristicUUID, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Read {@link Characteristic} value. - * - * @param {Identifier} characteristicIdentifier {@link Characteristic} ID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified ID. - * Latest value of {@link Characteristic} will be stored inside returned object. - * @private - */ - async _readCharacteristic( - characteristicIdentifier: Identifier, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.readCharacteristic(characteristicIdentifier, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Write {@link Characteristic} value with response. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - async writeCharacteristicWithResponseForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - base64Value: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.writeCharacteristicForDevice( - deviceIdentifier, - serviceUUID, - characteristicUUID, - base64Value, - true, - transactionId - ) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Write {@link Characteristic} value with response. - * - * @param {Identifier} serviceIdentifier {@link Service} ID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - * @private - */ - async _writeCharacteristicWithResponseForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - base64Value: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.writeCharacteristicForService(serviceIdentifier, characteristicUUID, base64Value, true, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Write {@link Characteristic} value with response. - * - * @param {Identifier} characteristicIdentifier {@link Characteristic} ID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified ID. - * Latest value of characteristic may not be stored inside returned object. - * @private - */ - async _writeCharacteristicWithResponse( - characteristicIdentifier: Identifier, - base64Value: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.writeCharacteristic(characteristicIdentifier, base64Value, true, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Write {@link Characteristic} value without response. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - async writeCharacteristicWithoutResponseForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - base64Value: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.writeCharacteristicForDevice( - deviceIdentifier, - serviceUUID, - characteristicUUID, - base64Value, - false, - transactionId - ) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Write {@link Characteristic} value without response. - * - * @param {Identifier} serviceIdentifier {@link Service} ID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - * @private - */ - async _writeCharacteristicWithoutResponseForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - base64Value: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.writeCharacteristicForService(serviceIdentifier, characteristicUUID, base64Value, false, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Write {@link Characteristic} value without response. - * - * @param {Identifier} characteristicIdentifier {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified ID. - * Latest value of characteristic may not be stored inside returned object. - * @private - */ - async _writeCharacteristicWithoutResponse( - characteristicIdentifier: Identifier, - base64Value: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeCharacteristic = await this._callPromise( - BleModule.writeCharacteristic(characteristicIdentifier, base64Value, false, transactionId) - ) - return new Characteristic(nativeCharacteristic, this) - } - - /** - * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used - * in favour of indications. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitorCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - listener: (error: ?BleError, characteristic: ?Characteristic) => void, - transactionId: ?TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Subscription { - const filledTransactionId = transactionId || this._nextUniqueID() - const commonArgs = [deviceIdentifier, serviceUUID, characteristicUUID, filledTransactionId] - const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] - - return this._handleMonitorCharacteristic( - BleModule.monitorCharacteristicForDevice(...args), - filledTransactionId, - listener - ) - } - - /** - * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used - * in favour of indications. - * - * @param {Identifier} serviceIdentifier {@link Service} ID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - * @private - */ - _monitorCharacteristicForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - listener: (error: ?BleError, characteristic: ?Characteristic) => void, - transactionId: ?TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Subscription { - const filledTransactionId = transactionId || this._nextUniqueID() - const commonArgs = [serviceIdentifier, characteristicUUID, filledTransactionId] - const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] - - return this._handleMonitorCharacteristic( - BleModule.monitorCharacteristicForService(...args), - filledTransactionId, - listener - ) - } - - /** - * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used - * in favour of indications. - * - * @param {Identifier} characteristicIdentifier - {@link Characteristic} ID. - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - * @private - */ - _monitorCharacteristic( - characteristicIdentifier: Identifier, - listener: (error: ?BleError, characteristic: ?Characteristic) => void, - transactionId: ?TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Subscription { - const filledTransactionId = transactionId || this._nextUniqueID() - const commonArgs = [characteristicIdentifier, filledTransactionId] - const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] - - return this._handleMonitorCharacteristic(BleModule.monitorCharacteristic(...args), filledTransactionId, listener) - } - - /** - * Common code to handle characteristic monitoring. - * - * @param {Promise} monitorPromise Characteristic monitoring promise - * @param {TransactionId} transactionId TransactionId of passed promise - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - * @private - */ - _handleMonitorCharacteristic( - monitorPromise: Promise, - transactionId: TransactionId, - listener: (error: ?BleError, characteristic: ?Characteristic) => void - ): Subscription { - const monitorListener = ([error, characteristic, msgTransactionId]: [ - ?string, - NativeCharacteristic, - TransactionId - ]) => { - if (transactionId !== msgTransactionId) { - return - } - if (error) { - listener(parseBleError(error, this._errorCodesToMessagesMapping), null) - return - } - listener(null, new Characteristic(characteristic, this)) - } - - const subscription: Subscription = this._eventEmitter.addListener(BleModule.ReadEvent, monitorListener) - - const id = this._nextUniqueID() - const wrappedSubscription: Subscription = { - remove: () => { - if (this._activeSubscriptions[id] != null) { - delete this._activeSubscriptions[id] - subscription.remove() - } - } - } - this._activeSubscriptions[id] = wrappedSubscription - - this._callPromise(monitorPromise).then( - () => { - wrappedSubscription.remove() - }, - (error: BleError) => { - listener(error, null) - wrappedSubscription.remove() - } - ) - - return { - remove: () => { - BleModule.cancelTransaction(transactionId) - } - } - } - - // Mark: Descriptors operations ---------------------------------------------------------------------------------- - - /** - * Read {@link Descriptor} value. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - async readDescriptorForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.readDescriptorForDevice( - deviceIdentifier, - serviceUUID, - characteristicUUID, - descriptorUUID, - transactionId - ) - ) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Read {@link Descriptor} value. - * - * @param {Identifier} serviceIdentifier {@link Service} identifier. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - * @private - */ - async _readDescriptorForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.readDescriptorForService(serviceIdentifier, characteristicUUID, descriptorUUID, transactionId) - ) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Read {@link Descriptor} value. - * - * @param {Identifier} characteristicIdentifier {@link Characteristic} identifier. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - * @private - */ - async _readDescriptorForCharacteristic( - characteristicIdentifier: Identifier, - descriptorUUID: UUID, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.readDescriptorForCharacteristic(characteristicIdentifier, descriptorUUID, transactionId) - ) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Read {@link Descriptor} value. - * - * @param {Identifier} descriptorIdentifier {@link Descriptor} identifier. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - * @private - */ - async _readDescriptor(descriptorIdentifier: Identifier, transactionId: ?TransactionId): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise(BleModule.readDescriptor(descriptorIdentifier, transactionId)) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Write {@link Descriptor} value. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - */ - async writeDescriptorForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.writeDescriptorForDevice( - deviceIdentifier, - serviceUUID, - characteristicUUID, - descriptorUUID, - valueBase64, - transactionId - ) - ) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Write {@link Descriptor} value. - * - * @param {Identifier} serviceIdentifier Service identifier - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - async _writeDescriptorForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.writeDescriptorForService( - serviceIdentifier, - characteristicUUID, - descriptorUUID, - valueBase64, - transactionId - ) - ) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Write {@link Descriptor} value. - * - * @param {Identifier} characteristicIdentifier Characteristic identifier - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - async _writeDescriptorForCharacteristic( - characteristicIdentifier: Identifier, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.writeDescriptorForCharacteristic(characteristicIdentifier, descriptorUUID, valueBase64, transactionId) - ) - return new Descriptor(nativeDescriptor, this) - } - - /** - * Write {@link Descriptor} value. - * - * @param {Identifier} descriptorIdentifier Descriptor identifier - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - async _writeDescriptor( - descriptorIdentifier: Identifier, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - if (!transactionId) { - transactionId = this._nextUniqueID() - } - const nativeDescriptor = await this._callPromise( - BleModule.writeDescriptor(descriptorIdentifier, valueBase64, transactionId) - ) - return new Descriptor(nativeDescriptor, this) - } -} diff --git a/src/BleManager.ts b/src/BleManager.ts new file mode 100644 index 000000000..b26a01a67 --- /dev/null +++ b/src/BleManager.ts @@ -0,0 +1,713 @@ +import { TurboModuleRegistry, Platform } from 'react-native' +import type { EventSubscription, TurboModule } from 'react-native' +import type { State, ScanOptions, ConnectOptions, ConnectionPriority } from './types' +import { BleError, BleErrorCode } from './BleError' +import { EventBatcher } from './EventBatcher' + +// --------------------------------------------------------------------------- +// Native module types (mirrored from specs/NativeBlePlx.ts since specs/ is +// excluded from tsconfig) +// --------------------------------------------------------------------------- + +type NativeEventEmitter = (handler: (arg: T) => void | Promise) => EventSubscription + +interface DeviceInfo { + readonly id: string + readonly name: string | null + readonly rssi: number + readonly mtu: number + readonly isConnectable: boolean | null + readonly serviceUuids: readonly string[] + readonly manufacturerData: string | null +} + +interface CharacteristicInfo { + readonly deviceId: string + readonly serviceUuid: string + readonly uuid: string + readonly value: string | null + readonly isNotifying: boolean + readonly isIndicatable: boolean + readonly isReadable: boolean + readonly isWritableWithResponse: boolean + readonly isWritableWithoutResponse: boolean +} + +interface ScanResult { + readonly id: string + readonly name: string | null + readonly rssi: number + readonly serviceUuids: readonly string[] + readonly manufacturerData: string | null +} + +interface ConnectionStateEvent { + readonly deviceId: string + readonly state: string + readonly errorCode: number | null + readonly errorMessage: string | null +} + +interface CharacteristicValueEvent { + readonly deviceId: string + readonly serviceUuid: string + readonly characteristicUuid: string + readonly value: string + readonly transactionId: string | null +} + +interface StateChangeEvent { + readonly state: string +} + +interface BleErrorInfo { + readonly code: number + readonly message: string + readonly isRetryable: boolean + readonly deviceId: string | null + readonly serviceUuid: string | null + readonly characteristicUuid: string | null + readonly operation: string | null + readonly platform: string + readonly nativeDomain: string | null + readonly nativeCode: number | null + readonly gattStatus: number | null + readonly attErrorCode: number | null +} + +interface BondStateEvent { + readonly deviceId: string + readonly bondState: string +} + +interface ConnectionEvent { + readonly deviceId: string + readonly connectionState: string +} + +interface RestoreStateEvent { + readonly devices: readonly DeviceInfo[] +} + +interface L2CAPChannelEvent { + readonly channelId: number + readonly deviceId: string + readonly psm: number +} + +interface PhyInfo { + readonly deviceId: string + readonly txPhy: number + readonly rxPhy: number +} + +interface NativeBlePlxSpec extends TurboModule { + createClient(restoreStateIdentifier: string | null): Promise + destroyClient(): Promise + state(): Promise + startDeviceScan( + uuids: readonly string[] | null, + options: Readonly<{ + scanMode?: number + callbackType?: number + legacyScan?: boolean + allowDuplicates?: boolean + }> | null + ): void + stopDeviceScan(): Promise + connectToDevice( + deviceId: string, + options: Readonly<{ + autoConnect?: boolean + timeout?: number + retries?: number + retryDelay?: number + requestMtu?: number + }> | null + ): Promise + cancelDeviceConnection(deviceId: string): Promise + isDeviceConnected(deviceId: string): Promise + discoverAllServicesAndCharacteristics(deviceId: string, transactionId: string | null): Promise + readCharacteristic( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + transactionId: string | null + ): Promise + writeCharacteristic( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + value: string, + withResponse: boolean, + transactionId: string | null + ): Promise + monitorCharacteristic( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + subscriptionType: string | null, + transactionId: string | null + ): void + requestMtu(deviceId: string, mtu: number, transactionId: string | null): Promise + requestConnectionPriority(deviceId: string, priority: number): Promise + cancelTransaction(transactionId: string): Promise + getMtu(deviceId: string): Promise + requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise + readPhy(deviceId: string): Promise + getBondedDevices(): Promise + getAuthorizationStatus(): Promise + openL2CAPChannel(deviceId: string, psm: number): Promise + writeL2CAPChannel(channelId: number, data: string): Promise + closeL2CAPChannel(channelId: number): Promise + + readonly onScanResult: NativeEventEmitter + readonly onConnectionStateChange: NativeEventEmitter + readonly onCharacteristicValueUpdate: NativeEventEmitter + readonly onStateChange: NativeEventEmitter + readonly onError: NativeEventEmitter + readonly onRestoreState: NativeEventEmitter + readonly onBondStateChange: NativeEventEmitter + readonly onConnectionEvent: NativeEventEmitter + readonly onL2CAPData: NativeEventEmitter<{ readonly channelId: number; readonly data: string }> + readonly onL2CAPClose: NativeEventEmitter<{ readonly channelId: number; readonly error: string | null }> +} + +// --------------------------------------------------------------------------- +// Public types +// --------------------------------------------------------------------------- + +export type { + DeviceInfo, + CharacteristicInfo, + ScanResult, + ConnectionStateEvent, + CharacteristicValueEvent, + BondStateEvent, + ConnectionEvent, + RestoreStateEvent, + L2CAPChannelEvent, + PhyInfo +} + +export interface Subscription { + remove(): void +} + +export interface MonitorOptions { + transactionId?: string + batchInterval?: number + subscriptionType?: 'notify' | 'indicate' | null +} + +export interface BleManagerOptions { + scanBatchIntervalMs?: number +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +let transactionCounter = 0 +function nextTransactionId(): string { + transactionCounter += 1 + return `__ble_tx_${transactionCounter}` +} + +function getNativeModule(): NativeBlePlxSpec { + const mod = TurboModuleRegistry.get('NativeBlePlx') + if (!mod) { + throw new Error( + 'react-native-ble-plx: NativeBlePlx TurboModule not found. Make sure the native module is linked correctly.' + ) + } + return mod +} + +// --------------------------------------------------------------------------- +// BleManager +// --------------------------------------------------------------------------- + +export class BleManager { + private cachedNativeModule: NativeBlePlxSpec | null = null + private scanBatcher: EventBatcher | null = null + private scanSubscription: EventSubscription | null = null + private errorSubscription: EventSubscription | null = null + private monitorBatchers = new Map>() + private monitorSubscriptions = new Map() + private activeTransactionIds = new Set() + private consumerSubscriptions = new Set() + private scanBatchIntervalMs: number + + constructor(options?: BleManagerOptions) { + this.scanBatchIntervalMs = options?.scanBatchIntervalMs ?? 100 + } + + private get nativeModule(): NativeBlePlxSpec { + if (!this.cachedNativeModule) { + this.cachedNativeModule = getNativeModule() + } + return this.cachedNativeModule + } + + // ----------------------------------------------------------------------- + // Lifecycle + // ----------------------------------------------------------------------- + + async createClient(restoreStateIdentifier: string | null = null): Promise { + // Clean up any existing error subscription to avoid leaks (Bug 6) + this.errorSubscription?.remove() + this.errorSubscription = null + + await this.nativeModule.createClient(restoreStateIdentifier) + + // Subscribe to native error events globally + this.errorSubscription = this.nativeModule.onError(_info => { + // Errors are routed through individual operation promises on the native side. + // This listener is kept so consumers can add a global handler in the future. + }) + } + + async destroyClient(): Promise { + // Dispose scan batcher & subscription + this.scanBatcher?.dispose() + this.scanBatcher = null + this.scanSubscription?.remove() + this.scanSubscription = null + + // Dispose all monitor batchers & subscriptions + this.monitorBatchers.forEach(batcher => batcher.dispose()) + this.monitorBatchers.clear() + + this.monitorSubscriptions.forEach(sub => sub.remove()) + this.monitorSubscriptions.clear() + + // Clean up all consumer subscriptions (Bug 3) + this.consumerSubscriptions.forEach(sub => sub.remove()) + this.consumerSubscriptions.clear() + + // Cancel all active transactions + const cancelPromises = [...this.activeTransactionIds].map(txId => + this.nativeModule.cancelTransaction(txId).catch(() => { + // Best-effort cancellation — ignore errors from already-completed transactions + }) + ) + await Promise.all(cancelPromises) + this.activeTransactionIds.clear() + + // Remove global error listener + this.errorSubscription?.remove() + this.errorSubscription = null + + await this.nativeModule.destroyClient() + } + + // ----------------------------------------------------------------------- + // State + // ----------------------------------------------------------------------- + + async state(): Promise { + const rawState = await this.nativeModule.state() + return rawState as State + } + + onStateChange(callback: (state: State) => void, emitCurrentState?: boolean): Subscription { + let removed = false + + const sub = this.nativeModule.onStateChange(event => { + if (!removed) { + callback(event.state as State) + } + }) + + if (emitCurrentState) { + this.nativeModule.state().then( + s => { + if (!removed) callback(s as State) + }, + () => { + // Ignore errors when reading initial state + } + ) + } + + const subscription: Subscription = { + remove: () => { + if (removed) return + removed = true + sub.remove() + this.consumerSubscriptions.delete(subscription) + } + } + + this.consumerSubscriptions.add(subscription) + return subscription + } + + // ----------------------------------------------------------------------- + // Scanning + // ----------------------------------------------------------------------- + + startDeviceScan( + serviceUuids: string[] | null, + options: ScanOptions | null, + callback: (error: BleError | null, scannedDevice: ScanResult | null) => void + ): void { + // Clean up any previous scan + this.stopScanInternal() + + // Create batcher for scan results + this.scanBatcher = new EventBatcher(this.scanBatchIntervalMs, 50, batch => { + batch.forEach(result => callback(null, result)) + }) + + // Listen to native scan events + this.scanSubscription = this.nativeModule.onScanResult(result => { + this.scanBatcher?.push(result) + }) + + // Start native scan (synchronous — errors come via onError) + // Pass empty object instead of null — iOS Codegen struct crashes on null backing dictionary + this.nativeModule.startDeviceScan(serviceUuids, options ?? {}) + } + + async stopDeviceScan(): Promise { + this.stopScanInternal() + await this.nativeModule.stopDeviceScan() + } + + private stopScanInternal(): void { + this.scanBatcher?.dispose() + this.scanBatcher = null + this.scanSubscription?.remove() + this.scanSubscription = null + } + + // ----------------------------------------------------------------------- + // Connection + // ----------------------------------------------------------------------- + + async connectToDevice(deviceId: string, options?: ConnectOptions | null): Promise { + // Pass empty object instead of null — iOS Codegen struct crashes on null backing dictionary + return this.nativeModule.connectToDevice(deviceId, options ?? {}) + } + + async cancelDeviceConnection(deviceId: string): Promise { + return this.nativeModule.cancelDeviceConnection(deviceId) + } + + async isDeviceConnected(deviceId: string): Promise { + return this.nativeModule.isDeviceConnected(deviceId) + } + + onDeviceDisconnected( + deviceId: string, + callback: (error: BleError | null, device: ConnectionStateEvent | null) => void + ): Subscription { + const sub = this.nativeModule.onConnectionStateChange(event => { + if (event.deviceId !== deviceId) return + if (event.state !== 'disconnected') return + + if (event.errorCode != null && event.errorMessage != null) { + const bleError = new BleError({ + code: event.errorCode, + message: event.errorMessage, + isRetryable: false, + platform: Platform.OS === 'ios' ? 'ios' : 'android', + deviceId: event.deviceId + }) + callback(bleError, event) + } else { + callback(null, event) + } + }) + + const subscription: Subscription = { + remove: () => { + sub.remove() + this.consumerSubscriptions.delete(subscription) + } + } + + this.consumerSubscriptions.add(subscription) + return subscription + } + + // ----------------------------------------------------------------------- + // Discovery + // ----------------------------------------------------------------------- + + async discoverAllServicesAndCharacteristics(deviceId: string, transactionId?: string | null): Promise { + return this.nativeModule.discoverAllServicesAndCharacteristics(deviceId, transactionId ?? null) + } + + // ----------------------------------------------------------------------- + // Read / Write + // ----------------------------------------------------------------------- + + async readCharacteristicForDevice( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + transactionId?: string | null + ): Promise { + return this.nativeModule.readCharacteristic(deviceId, serviceUuid, characteristicUuid, transactionId ?? null) + } + + async writeCharacteristicForDevice( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + value: string, + withResponse: boolean, + transactionId?: string | null + ): Promise { + return this.nativeModule.writeCharacteristic( + deviceId, + serviceUuid, + characteristicUuid, + value, + withResponse, + transactionId ?? null + ) + } + + // ----------------------------------------------------------------------- + // Monitor + // ----------------------------------------------------------------------- + + monitorCharacteristicForDevice( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + listener: (error: BleError | null, characteristic: CharacteristicValueEvent | null) => void, + options?: MonitorOptions + ): Subscription { + const txId = options?.transactionId ?? nextTransactionId() + const batchInterval = options?.batchInterval ?? 0 + const subscriptionType = options?.subscriptionType ?? null + + // Clean up any existing monitor with this txId (Bug 4) + const existingBatcher = this.monitorBatchers.get(txId) + if (existingBatcher) { + existingBatcher.dispose() + this.monitorBatchers.delete(txId) + } + const existingSub = this.monitorSubscriptions.get(txId) + if (existingSub) { + existingSub.remove() + this.monitorSubscriptions.delete(txId) + } + + this.activeTransactionIds.add(txId) + + // Create batcher for this monitor (0 = immediate delivery) + const batcher = new EventBatcher(batchInterval, 50, batch => { + batch.forEach(event => listener(null, event)) + }) + this.monitorBatchers.set(txId, batcher) + + // Listen to native characteristic value events, filtered by key fields + const sub = this.nativeModule.onCharacteristicValueUpdate(event => { + if (event.deviceId !== deviceId) return + if (event.serviceUuid !== serviceUuid) return + if (event.characteristicUuid !== characteristicUuid) return + if (event.transactionId !== txId) return + + batcher.push(event) + }) + this.monitorSubscriptions.set(txId, sub) + + // Tell native to start monitoring + this.nativeModule.monitorCharacteristic(deviceId, serviceUuid, characteristicUuid, subscriptionType, txId) + + let removed = false + return { + remove: () => { + if (removed) return + removed = true + + // Dispose batcher + batcher.dispose() + this.monitorBatchers.delete(txId) + + // Remove JS listener + sub.remove() + this.monitorSubscriptions.delete(txId) + + // Cancel native transaction + this.activeTransactionIds.delete(txId) + this.nativeModule.cancelTransaction(txId).catch(() => { + // Best-effort cancellation + }) + } + } + } + + // ----------------------------------------------------------------------- + // MTU + // ----------------------------------------------------------------------- + + async requestMTUForDevice(deviceId: string, mtu: number, transactionId?: string | null): Promise { + return this.nativeModule.requestMtu(deviceId, mtu, transactionId ?? null) + } + + async getMtu(deviceId: string): Promise { + return this.nativeModule.getMtu(deviceId) + } + + // ----------------------------------------------------------------------- + // PHY + // ----------------------------------------------------------------------- + + async requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise { + return this.nativeModule.requestPhy(deviceId, txPhy, rxPhy) + } + + async readPhy(deviceId: string): Promise { + return this.nativeModule.readPhy(deviceId) + } + + // ----------------------------------------------------------------------- + // Bonding + // ----------------------------------------------------------------------- + + async getBondedDevices(): Promise { + return this.nativeModule.getBondedDevices() + } + + // ----------------------------------------------------------------------- + // Authorization + // ----------------------------------------------------------------------- + + async getAuthorizationStatus(): Promise { + return this.nativeModule.getAuthorizationStatus() + } + + // ----------------------------------------------------------------------- + // L2CAP + // ----------------------------------------------------------------------- + + async openL2CAPChannel(deviceId: string, psm: number): Promise { + return this.nativeModule.openL2CAPChannel(deviceId, psm) + } + + async writeL2CAPChannel(channelId: number, data: string): Promise { + return this.nativeModule.writeL2CAPChannel(channelId, data) + } + + async closeL2CAPChannel(channelId: number): Promise { + return this.nativeModule.closeL2CAPChannel(channelId) + } + + monitorL2CAPChannel( + channelId: number, + listener: (error: BleError | null, data: { channelId: number; data: string } | null) => void + ): Subscription { + let subscription: Subscription + + const dataSub = this.nativeModule.onL2CAPData(event => { + if (event.channelId === channelId) { + listener(null, { channelId: event.channelId, data: event.data }) + } + }) + + const closeSub = this.nativeModule.onL2CAPClose(event => { + if (event.channelId === channelId) { + if (event.error) { + listener( + new BleError({ + code: BleErrorCode.L2CAPChannelClosed, + message: event.error, + isRetryable: false, + platform: Platform.OS === 'ios' ? 'ios' : 'android' + }), + null + ) + } else { + // Notify listener of clean close + listener(null, null) + } + // Clean up native subscriptions + dataSub.remove() + closeSub.remove() + // Remove from tracked subscriptions so destroyClient doesn't double-remove + this.consumerSubscriptions.delete(subscription) + } + }) + + subscription = { + remove: () => { + dataSub.remove() + closeSub.remove() + this.consumerSubscriptions.delete(subscription) + } + } + + this.consumerSubscriptions.add(subscription) + return subscription + } + + // ----------------------------------------------------------------------- + // Connection Priority + // ----------------------------------------------------------------------- + + async requestConnectionPriority(deviceId: string, priority: ConnectionPriority): Promise { + return this.nativeModule.requestConnectionPriority(deviceId, priority) + } + + // ----------------------------------------------------------------------- + // Event subscriptions + // ----------------------------------------------------------------------- + + onRestoreState(callback: (event: RestoreStateEvent) => void): Subscription { + const sub = this.nativeModule.onRestoreState(callback) + + const subscription: Subscription = { + remove: () => { + sub.remove() + this.consumerSubscriptions.delete(subscription) + } + } + + this.consumerSubscriptions.add(subscription) + return subscription + } + + onBondStateChange(callback: (event: BondStateEvent) => void): Subscription { + const sub = this.nativeModule.onBondStateChange(callback) + + const subscription: Subscription = { + remove: () => { + sub.remove() + this.consumerSubscriptions.delete(subscription) + } + } + + this.consumerSubscriptions.add(subscription) + return subscription + } + + onConnectionEvent(callback: (event: ConnectionEvent) => void): Subscription { + const sub = this.nativeModule.onConnectionEvent(callback) + + const subscription: Subscription = { + remove: () => { + sub.remove() + this.consumerSubscriptions.delete(subscription) + } + } + + this.consumerSubscriptions.add(subscription) + return subscription + } + + // ----------------------------------------------------------------------- + // Cancellation + // ----------------------------------------------------------------------- + + async cancelTransaction(transactionId: string): Promise { + this.activeTransactionIds.delete(transactionId) + await this.nativeModule.cancelTransaction(transactionId) + } +} diff --git a/src/BleModule.js b/src/BleModule.js deleted file mode 100644 index c9c5ea030..000000000 --- a/src/BleModule.js +++ /dev/null @@ -1,857 +0,0 @@ -// @flow -'use strict' - -import { NativeModules, NativeEventEmitter } from 'react-native' -import { State, LogLevel, ConnectionPriority } from './TypeDefinition' -import type { - DeviceId, - Identifier, - UUID, - TransactionId, - CharacteristicSubscriptionType, - Base64, - ScanOptions, - ConnectionOptions -} from './TypeDefinition' - -/** - * Native device object passed from BleModule. - * @private - */ -export interface NativeDevice { - /** - * Device identifier: MAC address on Android and UUID on iOS. - * @private - */ - id: DeviceId; - /** - * Device name if present - * @private - */ - name: ?string; - /** - * Current Received Signal Strength Indication of device - * @private - */ - rssi: ?number; - /** - * Current Maximum Transmission Unit for this device. When device is not connected - * default value of 23 is used. - * @private - */ - mtu: number; - - // Advertisement - - /** - * Device's custom manufacturer data. Its format is defined by manufacturer. - * @private - */ - manufacturerData: ?Base64; - - /** - * Raw device scan data. When you have specific advertiser data, - * you can implement your own processing. - * @private - */ - rawScanRecord: Base64; - - /** - * Map of service UUIDs with associated data. - * @private - */ - serviceData: ?{ [uuid: UUID]: Base64 }; - - /** - * List of available services visible during scanning. - * @private - */ - serviceUUIDs: ?Array; - - /** - * User friendly name of device. - * @private - */ - localName: ?string; - - /** - * Transmission power level of device. - * @private - */ - txPowerLevel: ?number; - - /** - * List of solicited service UUIDs. - * @private - */ - solicitedServiceUUIDs: ?Array; - - /** - * Is device connectable. - * @private - */ - isConnectable: ?boolean; - - /** - * List of overflow service UUIDs. - * @private - */ - overflowServiceUUIDs: ?Array; -} - -/** - * Native service object passed from BleModule. - * @private - */ -export interface NativeService { - /** - * Service unique identifier - * @private - */ - id: Identifier; - /** - * Service UUID - * @private - */ - uuid: UUID; - /** - * Device's ID to which service belongs - * @private - */ - deviceID: DeviceId; - /** - * Value indicating whether the type of service is primary or secondary. - * @private - */ - isPrimary: boolean; -} - -/** - * Native characteristic object passed from BleModule. - * @private - */ -export interface NativeCharacteristic { - /** - * Characteristic unique identifier - * @private - */ - id: Identifier; - /** - * Characteristic UUID - * @private - */ - uuid: UUID; - /** - * Service's ID to which characteristic belongs - * @private - */ - serviceID: Identifier; - /** - * Service's UUID to which characteristic belongs - * @private - */ - serviceUUID: UUID; - /** - * Device's ID to which characteristic belongs - * @private - */ - deviceID: DeviceId; - /** - * True if characteristic can be read - * @private - */ - isReadable: boolean; - /** - * True if characteristic can be written with response - * @private - */ - isWritableWithResponse: boolean; - /** - * True if characteristic can be written without response - * @private - */ - isWritableWithoutResponse: boolean; - /** - * True if characteristic can monitor value changes. - * @private - */ - isNotifiable: boolean; - /** - * True if characteristic is monitoring value changes without ACK. - * @private - */ - isNotifying: boolean; - /** - * True if characteristic is monitoring value changes with ACK. - * @private - */ - isIndicatable: boolean; - /** - * Characteristic value if present - * @private - */ - value: ?Base64; -} - -/** - * Native descriptor object passed from BleModule. - * @private - */ -export interface NativeDescriptor { - /** - * Descriptor unique identifier - * @private - */ - id: Identifier; - /** - * Descriptor UUID - * @private - */ - uuid: UUID; - /** - * Characteristic's ID to which descriptor belongs - * @private - */ - characteristicID: Identifier; - /** - * Characteristic's UUID to which descriptor belongs - * @private - */ - characteristicUUID: UUID; - /** - * Service's ID to which descriptor belongs - * @private - */ - serviceID: Identifier; - /** - * Service's UUID to which descriptor belongs - * @private - */ - serviceUUID: UUID; - /** - * Device's ID to which descriptor belongs - * @private - */ - deviceID: DeviceId; - /** - * Descriptor value if present - * @private - */ - value: ?Base64; -} - -/** - * Object representing information about restored BLE state after application relaunch. - * @private - */ -export interface NativeBleRestoredState { - /** - * List of connected devices after state restoration. - * @type {Array} - * @instance - * @memberof NativeBleRestoredState - * @private - */ - connectedPeripherals: Array; -} - -/** - * Native BLE Module interface - * @private - */ -export interface BleModuleInterface { - // NativeModule methods - - addListener(string): void; - removeListeners(number): void; - - // Lifecycle - - /** - * Creates new native module internally. Only one module - * is allowed to be instantiated. - * @param {?string} restoreIdentifierKey Optional unique Id used for state restoration of BLE manager. - * @private - */ - createClient(restoreIdentifierKey: ?string): void; - - /** - * Destroys previously instantiated module. This function is - * only safe when previously BleModule was created. - * @returns {Promise} Promise may return an error when the function cannot be called. - * @private - */ - destroyClient(): Promise; - - // Monitoring state - - /** - * Enable Bluetooth. This function blocks until BLE is in PoweredOn state. [Android only] - * - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise completes when state transition was successful. - * @private - */ - enable(transactionId: TransactionId): Promise; - - /** - * Disable Bluetooth. This function blocks until BLE is in PoweredOff state. [Android only] - * - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise completes when state transition was successful. - * @private - */ - disable(transactionId: TransactionId): Promise; - - /** - * Current state of BLE device. - * - * @returns {Promise} Current state of BLE device. - * @private - */ - state(): Promise<$Keys>; - - // Scanning - - /** - * Starts device scan. - * - * @param {?Array} filteredUUIDs List of UUIDs for services which needs to be present to detect device during - * scanning. - * @param {?ScanOptions} options Platform dependent options - * @returns {Promise} the promise may be rejected if the operation is impossible to perform. - * @private - */ - startDeviceScan(filteredUUIDs: ?Array, options: ?ScanOptions): Promise; - - /** - * Stops device scan. - * @private - * @returns {Promise} the promise may be rejected if the operation is impossible to perform. - */ - stopDeviceScan(): Promise; - - // Device operations - - /** - * Request a connection parameter update. This functions may update connection parameters on Android API level 21 or - * above. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {ConnectionPriority} connectionPriority: Connection priority. - * @param {TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Connected device. - * @private - */ - requestConnectionPriorityForDevice( - deviceIdentifier: DeviceId, - connectionPriority: $Values, - transactionId: TransactionId - ): Promise; - - /** - * Reads RSSI for connected device. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Connected device with updated RSSI value. - * @private - */ - readRSSIForDevice(deviceIdentifier: DeviceId, transactionId: TransactionId): Promise; - - /** - * Request new MTU value for this device. This function currently is not doing anything - * on iOS platform as MTU exchange is done automatically. Since Android 14, - * mtu management has been changed, more information can be found at the link: - * https://developer.android.com/about/versions/14/behavior-changes-all#mtu-set-to-517 - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {number} mtu New MTU to negotiate. - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Device with updated MTU size. Default value is 23 (517 since Android 14).. - * @private - */ - requestMTUForDevice(deviceIdentifier: DeviceId, mtu: number, transactionId: TransactionId): Promise; - - // Device management - - /** - * Returns a list of known peripherals by their identifiers. - * @param {Array} deviceIdentifiers List of device identifiers - * @returns {Promise>} List of known devices by their identifiers. - * @private - */ - devices(deviceIdentifiers: Array): Promise>; - - /** - * Returns a list of the peripherals (containing any of the specified services) currently connected to the system - * which have discovered services. Returned devices **may not be connected** to your application. - * @param {Array} serviceUUIDs List of service UUIDs. Device must contain at least one of them to be listed. - * @returns {Promise>} List of known devices with discovered services as stated in the parameter. - * @private - */ - connectedDevices(serviceUUIDs: Array): Promise>; - - // Connection management - - /** - * Connect to specified device. - * - * @param {DeviceId} deviceIdentifier Device identifier to connect to. - * @param {?ConnectionOptions} options Connection options. - * @returns {Promise} Connected device. - * @private - */ - connectToDevice(deviceIdentifier: DeviceId, options: ?ConnectionOptions): Promise; - - /** - * Cancels pending device connection. - * - * @param {DeviceId} deviceIdentifier Device identifier which is already connected. - * @returns {Promise} Disconnected device. - * @private - */ - cancelDeviceConnection(deviceIdentifier: DeviceId): Promise; - - /** - * Checks if specified device is connected. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @returns {Promise} True if specified device is connected. - * @private - */ - isDeviceConnected(deviceIdentifier: DeviceId): Promise; - - // Discovery - - /** - * Discovers all services, characteristics and descriptors for specified device. - * - * @param {DeviceId} deviceIdentifier Connected device identifier. - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Device which has discovered characteristics and services. - * @private - */ - discoverAllServicesAndCharacteristicsForDevice( - deviceIdentifier: DeviceId, - transactionId: TransactionId - ): Promise; - - // Service and characteristic getters - - /** - * List of discovered services for specified device. - * - * @param {DeviceId} deviceIdentifier Connected device identifier. - * @returns {Promise>} List of services available in device. - * @private - */ - servicesForDevice(deviceIdentifier: DeviceId): Promise>; - - /** - * List of discovered characteristics for specified service. - * - * @param {DeviceId} deviceIdentifier Connected device identifier. - * @param {UUID} serviceUUID Service UUID which contains characteristics. - * @returns {Promise>} List of characteristics available in service. - * @private - */ - characteristicsForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID): Promise>; - - /** - * List of discovered characteristics for specified service. - * - * @param {Identifier} serviceIdentifier Service ID which contains characteristics. - * @returns {Promise>} List of characteristics available in service. - * @private - */ - characteristicsForService(serviceIdentifier: Identifier): Promise>; - - /** - * List of discovered descriptors for specified characteristic. - * - * @param {DeviceId} deviceIdentifier Connected device identifier. - * @param {UUID} serviceUUID Service UUID which contains descriptors. - * @param {UUID} characteristicUUID Characteristic UUID which contains descriptors. - * @returns {Promise>} List of descriptors available in characteristic. - * @private - */ - descriptorsForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID - ): Promise>; - - /** - * List of discovered descriptors for specified characteristic. - * - * @param {Identifier} serviceIdentifier Service identifier which contains descriptors. - * @param {UUID} characteristicUUID Characteristic UUID which contains descriptors. - * @returns {Promise>} List of descriptors available in characteristic. - * @private - */ - descriptorsForService(serviceIdentifier: Identifier, characteristicUUID: UUID): Promise>; - - /** - * List of discovered descriptors for specified characteristic. - * - * @param {Identifier} characteristicIdentifier Characteristic identifier which contains descriptors. - * @returns {Promise>} List of descriptors available in characteristic. - * @private - */ - descriptorsForCharacteristic(characteristicIdentifier: Identifier): Promise>; - - // Characteristics operations - - /** - * Read characteristic's value. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Characteristic for which value was read - * @private - */ - readCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - transactionId: TransactionId - ): Promise; - - /** - * Read characteristic's value. - * - * @param {Identifier} serviceIdentifier Service ID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Characteristic for which value was read - * @private - */ - readCharacteristicForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - transactionId: TransactionId - ): Promise; - - /** - * Read characteristic's value. - * - * @param {Identifier} characteristicIdentifer Characteristic ID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Characteristic for which value was read - * @private - */ - readCharacteristic(characteristicIdentifer: Identifier, transactionId: TransactionId): Promise; - - /** - * Write value to characteristic. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {boolean} withResponse True if write should be with response - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Characteristic which saved passed value - * @private - */ - writeCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - valueBase64: Base64, - withResponse: boolean, - transactionId: TransactionId - ): Promise; - - /** - * Write value to characteristic. - * - * @param {Identifier} serviceIdentifier Service ID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {boolean} withResponse True if write should be with response - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Characteristic which saved passed value - * @private - */ - writeCharacteristicForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - valueBase64: Base64, - withResponse: boolean, - transactionId: TransactionId - ): Promise; - - /** - * Write value to characteristic. - * - * @param {Identifier} characteristicIdentifier Characteristic ID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {boolean} withResponse True if write should be with response - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Characteristic which saved passed value - * @private - */ - writeCharacteristic( - characteristicIdentifier: Identifier, - valueBase64: Base64, - withResponse: boolean, - transactionId: TransactionId - ): Promise; - - /** - * Setup monitoring of characteristic value. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * @returns {Promise} Value which is returned when monitoring was cancelled or resulted in error - * @private - */ - monitorCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - transactionId: TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Promise; - - /** - * Setup monitoring of characteristic value. - * - * @param {Identifier} serviceIdentifier Service ID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * @returns {Promise} Value which is returned when monitoring was cancelled or resulted in error - * @private - */ - monitorCharacteristicForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - transactionId: TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Promise; - - /** - * Setup monitoring of characteristic value. - * - * @param {Identifier} characteristicIdentifier Characteristic ID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * @returns {Promise} Value which is returned when monitoring was cancelled or resulted in error - * @private - */ - monitorCharacteristic( - characteristicIdentifier: Identifier, - transactionId: TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Promise; - - // Descriptor operations - - /** - * Read descriptor's value. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor for which value was read - * @private - */ - readDescriptorForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId: TransactionId - ): Promise; - - /** - * Read descriptor's value. - * - * @param {Identifier} serviceIdentifier Service identifier - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor for which value was read - * @private - */ - readDescriptorForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId: TransactionId - ): Promise; - - /** - * Read descriptor's value. - * - * @param {Identifier} characteristicIdentifier Characteristic identifier - * @param {UUID} descriptorUUID Descriptor UUID - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor for which value was read - * @private - */ - readDescriptorForCharacteristic( - characteristicIdentifier: Identifier, - descriptorUUID: UUID, - transactionId: TransactionId - ): Promise; - - /** - * Read descriptor's value. - * - * @param {Identifier} descriptorIdentifier Descriptor identifier - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor for which value was read - * @private - */ - readDescriptor(descriptorIdentifier: Identifier, transactionId: TransactionId): Promise; - - /** - * Write value to descriptor. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - writeDescriptorForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: TransactionId - ): Promise; - - /** - * Write value to descriptor. - * - * @param {Identifier} serviceIdentifier Service identifier - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - writeDescriptorForService( - serviceIdentifier: Identifier, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: TransactionId - ): Promise; - - /** - * Write value to descriptor. - * - * @param {Identifier} characteristicIdentifier Characteristic identifier - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - writeDescriptorForCharacteristic( - characteristicIdentifier: Identifier, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: TransactionId - ): Promise; - - /** - * Write value to descriptor. - * - * @param {Identifier} descriptorIdentifier Descriptor identifier - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - * @private - */ - writeDescriptor( - descriptorIdentifier: Identifier, - valueBase64: Base64, - transactionId: TransactionId - ): Promise; - - // Other APIs - - /** - * Cancels specified transaction - * - * @param {TransactionId} transactionId Transaction handle for operation to be cancelled - * @returns {Promise} - * @private - */ - cancelTransaction(transactionId: TransactionId): Promise; - - /** - * Sets new log level for native module's logging mechanism. - * @param {LogLevel} logLevel New log level to be set. - * @returns {Promise} Current log level. - * @private - */ - setLogLevel(logLevel: $Keys): Promise<$Keys | void>; - - /** - * Get current log level for native module's logging mechanism. - * @returns {Promise} Current log level. - * @private - */ - logLevel(): Promise<$Keys>; - - // Events - - /** - * New scanned event arrived as [?Error, ?NativeDevice] object. - * @private - */ - ScanEvent: string; - - /** - * Characteristic value update broadcasted due to registered notification as - * [?Error, ?NativeCharacteristic, ?TransactionId]. - * @private - */ - ReadEvent: string; - - /** - * BLE Manager changed its state as $Keys - * @private - */ - StateChangeEvent: string; - - /** - * BLE Manager restored its internal state - * @private - */ - RestoreStateEvent: string; - - /** - * Device disconnected as [Error?, NativeDevice] - * @private - */ - DisconnectionEvent: string; -} - -/** - * Native module provider - * - * @private - */ -export const BleModule: BleModuleInterface = NativeModules.BlePlx - -export const EventEmitter = NativeEventEmitter diff --git a/src/Characteristic.js b/src/Characteristic.js deleted file mode 100644 index 492cbb975..000000000 --- a/src/Characteristic.js +++ /dev/null @@ -1,180 +0,0 @@ -// @flow -'use strict' - -import type { BleManager } from './BleManager' -import type { BleError } from './BleError' -import { Descriptor } from './Descriptor' -import type { NativeCharacteristic } from './BleModule' -import type { - DeviceId, - Identifier, - UUID, - TransactionId, - CharacteristicSubscriptionType, - Base64, - Subscription -} from './TypeDefinition' -import { isIOS } from './Utils' - -/** - * Characteristic object. - */ -export class Characteristic implements NativeCharacteristic { - /** - * Internal BLE Manager handle - * @private - */ - _manager: BleManager - /** - * Characteristic unique identifier - */ - id: Identifier - /** - * Characteristic UUID - */ - uuid: UUID - /** - * Service's ID to which characteristic belongs - */ - serviceID: Identifier - /** - * Service's UUID to which characteristic belongs - */ - serviceUUID: UUID - /** - * Device's ID to which characteristic belongs - */ - deviceID: DeviceId - /** - * True if characteristic can be read - */ - isReadable: boolean - /** - * True if characteristic can be written with response - */ - isWritableWithResponse: boolean - /** - * True if characteristic can be written without response - */ - isWritableWithoutResponse: boolean - /** - * True if characteristic can monitor value changes. - */ - isNotifiable: boolean - /** - * True if characteristic is monitoring value changes without ACK. - */ - isNotifying: boolean - /** - * True if characteristic is monitoring value changes with ACK. - */ - isIndicatable: boolean - /** - * Characteristic value if present - */ - value: ?Base64 - - /** - * Private constructor used to create instance of {@link Characteristic}. - * @param {NativeCharacteristic} nativeCharacteristic NativeCharacteristic - * @param {BleManager} manager BleManager - * @private - */ - constructor(nativeCharacteristic: NativeCharacteristic, manager: BleManager) { - Object.assign(this, nativeCharacteristic) - Object.defineProperty(this, '_manager', { value: manager, enumerable: false }) - } - - /** - * {@link #blemanagerdescriptorsfordevice|bleManager.descriptorsForDevice()} with partially filled arguments. - * - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for this {@link Characteristic}. - */ - descriptors(): Promise> { - return this._manager._descriptorsForCharacteristic(this.id) - } - - /** - * {@link #blemanagerreadcharacteristicfordevice|bleManager.readCharacteristicForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits this {@link Characteristic}. Latest value will be stored - * inside returned object. - */ - read(transactionId: ?TransactionId): Promise { - return this._manager._readCharacteristic(this.id, transactionId) - } - - /** - * {@link #blemanagerwritecharacteristicwithresponsefordevice|bleManager.writeCharacteristicWithResponseForDevice()} with partially filled arguments. - * - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits this {@link Characteristic}. Latest value may - * not be stored inside returned object. - */ - writeWithResponse(valueBase64: Base64, transactionId: ?TransactionId): Promise { - return this._manager._writeCharacteristicWithResponse(this.id, valueBase64, transactionId) - } - - /** - * {@link #blemanagerwritecharacteristicwithoutresponsefordevice|bleManager.writeCharacteristicWithoutResponseForDevice()} with partially filled arguments. - * - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits this {@link Characteristic}. Latest value may - * not be stored inside returned object. - */ - writeWithoutResponse(valueBase64: Base64, transactionId: ?TransactionId): Promise { - return this._manager._writeCharacteristicWithoutResponse(this.id, valueBase64, transactionId) - } - - /** - * {@link #blemanagermonitorcharacteristicfordevice|bleManager.monitorCharacteristicForDevice()} with partially filled arguments. - * - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener callback which emits - * this {@link Characteristic} with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitor( - listener: (error: ?BleError, characteristic: ?Characteristic) => void, - transactionId: ?TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Subscription { - const commonArgs = [this.id, listener, transactionId] - const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] - return this._manager._monitorCharacteristic(...args) - } - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - async readDescriptor(descriptorUUID: UUID, transactionId: ?TransactionId): Promise { - return this._manager._readDescriptorForCharacteristic(this.id, descriptorUUID, transactionId) - } - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - async writeDescriptor(descriptorUUID: UUID, valueBase64: Base64, transactionId: ?TransactionId): Promise { - return this._manager._writeDescriptorForCharacteristic(this.id, descriptorUUID, valueBase64, transactionId) - } -} diff --git a/src/Characteristic.ts b/src/Characteristic.ts new file mode 100644 index 000000000..f2bc4fb81 --- /dev/null +++ b/src/Characteristic.ts @@ -0,0 +1,69 @@ +import type { + BleManager, + CharacteristicInfo, + Subscription, + MonitorOptions, + CharacteristicValueEvent +} from './BleManager' +import type { BleError } from './BleError' + +export class Characteristic { + readonly deviceId: string + readonly serviceUuid: string + readonly uuid: string + readonly value: string | null + readonly isNotifying: boolean + readonly isIndicatable: boolean + readonly isReadable: boolean + readonly isWritableWithResponse: boolean + readonly isWritableWithoutResponse: boolean + + private manager: BleManager + + constructor(info: CharacteristicInfo, manager: BleManager) { + this.deviceId = info.deviceId + this.serviceUuid = info.serviceUuid + this.uuid = info.uuid + this.value = info.value + this.isNotifying = info.isNotifying + this.isIndicatable = info.isIndicatable + this.isReadable = info.isReadable + this.isWritableWithResponse = info.isWritableWithResponse + this.isWritableWithoutResponse = info.isWritableWithoutResponse + this.manager = manager + } + + async read(): Promise { + const info = await this.manager.readCharacteristicForDevice(this.deviceId, this.serviceUuid, this.uuid) + return new Characteristic(info, this.manager) + } + + async writeWithResponse(value: string): Promise { + const info = await this.manager.writeCharacteristicForDevice( + this.deviceId, + this.serviceUuid, + this.uuid, + value, + true + ) + return new Characteristic(info, this.manager) + } + + async writeWithoutResponse(value: string): Promise { + const info = await this.manager.writeCharacteristicForDevice( + this.deviceId, + this.serviceUuid, + this.uuid, + value, + false + ) + return new Characteristic(info, this.manager) + } + + monitor( + listener: (error: BleError | null, characteristic: CharacteristicValueEvent | null) => void, + options?: MonitorOptions + ): Subscription { + return this.manager.monitorCharacteristicForDevice(this.deviceId, this.serviceUuid, this.uuid, listener, options) + } +} diff --git a/src/Descriptor.js b/src/Descriptor.js deleted file mode 100644 index d8d476222..000000000 --- a/src/Descriptor.js +++ /dev/null @@ -1,83 +0,0 @@ -// @flow -'use strict' - -import type { BleManager } from './BleManager' -import type { NativeDescriptor } from './BleModule' -import type { DeviceId, Identifier, UUID, TransactionId, Base64 } from './TypeDefinition' - -/** - * Descriptor object. - */ -export class Descriptor implements NativeDescriptor { - /** - * Internal BLE Manager handle - * @private - */ - _manager: BleManager - /** - * Descriptor unique identifier - */ - id: Identifier - /** - * Descriptor UUID - */ - uuid: UUID - /** - * Characteristic's ID to which descriptor belongs - */ - characteristicID: Identifier - /** - * Characteristic's UUID to which descriptor belongs - */ - characteristicUUID: UUID - /** - * Service's ID to which descriptor belongs - */ - serviceID: Identifier - /** - * Service's UUID to which descriptor belongs - */ - serviceUUID: UUID - /** - * Device's ID to which descriptor belongs - */ - deviceID: DeviceId - /** - * Descriptor value if present - */ - value: ?Base64 - - /** - * Private constructor used to create instance of {@link Descriptor}. - * @param {NativeDescriptor} nativeDescriptor NativeDescriptor - * @param {BleManager} manager BleManager - * @private - */ - constructor(nativeDescriptor: NativeDescriptor, manager: BleManager) { - Object.assign(this, nativeDescriptor) - Object.defineProperty(this, '_manager', { value: manager, enumerable: false }) - } - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - async read(transactionId: ?TransactionId): Promise { - return this._manager._readDescriptor(this.id, transactionId) - } - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - async write(valueBase64: Base64, transactionId: ?TransactionId): Promise { - return this._manager._writeDescriptor(this.id, valueBase64, transactionId) - } -} diff --git a/src/Descriptor.ts b/src/Descriptor.ts new file mode 100644 index 000000000..5144fbe73 --- /dev/null +++ b/src/Descriptor.ts @@ -0,0 +1,15 @@ +export class Descriptor { + readonly uuid: string + readonly characteristicUuid: string + readonly serviceUuid: string + readonly deviceId: string + readonly value: string | null + + constructor(uuid: string, characteristicUuid: string, serviceUuid: string, deviceId: string, value: string | null) { + this.uuid = uuid + this.characteristicUuid = characteristicUuid + this.serviceUuid = serviceUuid + this.deviceId = deviceId + this.value = value + } +} diff --git a/src/Device.js b/src/Device.js deleted file mode 100644 index 59c72bed8..000000000 --- a/src/Device.js +++ /dev/null @@ -1,379 +0,0 @@ -// @flow -'use strict' - -import type { BleManager } from './BleManager' -import type { BleError } from './BleError' -import type { Characteristic } from './Characteristic' -import type { Service } from './Service' -import type { Descriptor } from './Descriptor' -import { ConnectionPriority } from './TypeDefinition' -import type { NativeDevice } from './BleModule' -import type { - DeviceId, - Base64, - UUID, - Subscription, - TransactionId, - CharacteristicSubscriptionType, - ConnectionOptions -} from './TypeDefinition' -import { isIOS } from './Utils' - -/** - * Device instance which can be retrieved only by calling - * {@link #blemanagerstartdevicescan|bleManager.startDeviceScan()}. - */ -export class Device implements NativeDevice { - /** - * Internal BLE Manager handle - * @private - */ - _manager: BleManager - - /** - * Device identifier: MAC address on Android and UUID on iOS. - */ - id: DeviceId - - /** - * Device name if present - */ - name: ?string - - /** - * Current Received Signal Strength Indication of device - */ - rssi: ?number - - /** - * Current Maximum Transmission Unit for this device. When device is not connected - * default value of 23 is used. - */ - mtu: number - - // Advertisement - - /** - * Device's custom manufacturer data. Its format is defined by manufacturer. - */ - manufacturerData: ?Base64 - - /** - * Raw device scan data. When you have specific advertiser data, - * you can implement your own processing. - */ - rawScanRecord: Base64 - - /** - * Map of service UUIDs (as keys) with associated data (as values). - */ - serviceData: ?{ [uuid: UUID]: Base64 } - - /** - * List of available services visible during scanning. - */ - serviceUUIDs: ?Array - - /** - * User friendly name of device. - */ - localName: ?string - - /** - * Transmission power level of device. - */ - txPowerLevel: ?number - - /** - * List of solicited service UUIDs. - */ - solicitedServiceUUIDs: ?Array - - /** - * Is device connectable. [iOS only] - */ - isConnectable: ?boolean - - /** - * List of overflow service UUIDs. [iOS only] - */ - overflowServiceUUIDs: ?Array - - /** - * Private constructor used to create {@link Device} object. - * - * @param {NativeDevice} nativeDevice Native device properties - * @param {BleManager} manager {@link BleManager} handle - * @private - */ - constructor(nativeDevice: NativeDevice, manager: BleManager) { - Object.assign(this, nativeDevice) - Object.defineProperty(this, '_manager', { value: manager, enumerable: false }) - } - - /** - * {@link #blemanagerrequestconnectionpriorityfordevice|bleManager.requestConnectionPriorityForDevice()} with partially filled arguments. - * - * @param {ConnectionPriority} connectionPriority: Connection priority. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Connected device. - */ - requestConnectionPriority( - connectionPriority: $Values, - transactionId: ?TransactionId - ): Promise { - return this._manager.requestConnectionPriorityForDevice(this.id, connectionPriority, transactionId) - } - - /** - * {@link #blemanagerreadrssifordevice|bleManager.readRSSIForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} This device with updated RSSI value. - */ - readRSSI(transactionId: ?TransactionId): Promise { - return this._manager.readRSSIForDevice(this.id, transactionId) - } - - /** - * {@link #blemanagerrequestmtufordevice|bleManager.requestMTUForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Device with updated MTU size. Default value is 23. - */ - requestMTU(mtu: number, transactionId: ?TransactionId): Promise { - return this._manager.requestMTUForDevice(this.id, mtu, transactionId) - } - - /** - * {@link #blemanagerconnecttodevice|bleManager.connectToDevice()} with partially filled arguments. - * - * @param {?ConnectionOptions} options Platform specific options for connection establishment. Not used currently. - * @returns {Promise} Connected {@link Device} object if successful. - */ - connect(options: ?ConnectionOptions): Promise { - return this._manager.connectToDevice(this.id, options) - } - - /** - * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} with partially filled arguments. - * - * @returns {Promise} Returns closed {@link Device} when operation is successful. - */ - cancelConnection(): Promise { - return this._manager.cancelDeviceConnection(this.id) - } - - /** - * {@link #blemanagerisdeviceconnected|bleManager.isDeviceConnected()} with partially filled arguments. - * - * @returns {Promise} Promise which emits `true` if device is connected, and `false` otherwise. - */ - isConnected(): Promise { - return this._manager.isDeviceConnected(this.id) - } - - /** - * {@link #blemanagerondevicedisconnected|bleManager.onDeviceDisconnected()} with partially filled arguments. - * - * @param {function(error: ?BleError, device: Device)} listener callback returning error as a reason of disconnection - * if available and {@link Device} object. If an error is null, that means the connection was terminated by - * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} call. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - onDisconnected(listener: (error: ?BleError, device: Device) => void): Subscription { - return this._manager.onDeviceDisconnected(this.id, listener) - } - - /** - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|bleManager.discoverAllServicesAndCharacteristicsForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise which emits {@link Device} object if all available services and - * characteristics have been discovered. - */ - discoverAllServicesAndCharacteristics(transactionId: ?TransactionId): Promise { - return this._manager.discoverAllServicesAndCharacteristicsForDevice(this.id, transactionId) - } - - /** - * {@link #blemanagerservicesfordevice|bleManager.servicesForDevice()} with partially filled arguments. - * - * @returns {Promise} Promise which emits array of {@link Service} objects which are discovered by this - * device. - */ - services(): Promise { - return this._manager.servicesForDevice(this.id) - } - - /** - * {@link #blemanagercharacteristicsfordevice|bleManager.characteristicsForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @returns {Promise} Promise which emits array of {@link Characteristic} objects which are - * discovered for a {@link Device} in specified {@link Service}. - */ - characteristicsForService(serviceUUID: string): Promise { - return this._manager.characteristicsForDevice(this.id, serviceUUID) - } - - /** - * {@link #blemanagerdescriptorsfordevice|bleManager.descriptorsForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for this {@link Characteristic}. - */ - descriptorsForService(serviceUUID: UUID, characteristicUUID: UUID): Promise> { - return this._manager.descriptorsForDevice(this.id, serviceUUID, characteristicUUID) - } - - /** - * {@link #blemanagerreadcharacteristicfordevice|bleManager.readCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. - */ - readCharacteristicForService( - serviceUUID: UUID, - characteristicUUID: UUID, - transactionId: ?TransactionId - ): Promise { - return this._manager.readCharacteristicForDevice(this.id, serviceUUID, characteristicUUID, transactionId) - } - - /** - * {@link #blemanagerwritecharacteristicwithresponsefordevice|bleManager.writeCharacteristicWithResponseForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithResponseForService( - serviceUUID: UUID, - characteristicUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - return this._manager.writeCharacteristicWithResponseForDevice( - this.id, - serviceUUID, - characteristicUUID, - valueBase64, - transactionId - ) - } - - /** - * {@link #blemanagerwritecharacteristicwithoutresponsefordevice|bleManager.writeCharacteristicWithoutResponseForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithoutResponseForService( - serviceUUID: UUID, - characteristicUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - return this._manager.writeCharacteristicWithoutResponseForDevice( - this.id, - serviceUUID, - characteristicUUID, - valueBase64, - transactionId - ) - } - - /** - * {@link #blemanagermonitorcharacteristicfordevice|bleManager.monitorCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitorCharacteristicForService( - serviceUUID: UUID, - characteristicUUID: UUID, - listener: (error: ?BleError, characteristic: ?Characteristic) => void, - transactionId: ?TransactionId, - subscriptionType?: CharacteristicSubscriptionType - ): Subscription { - const commonArgs = [this.id, serviceUUID, characteristicUUID, listener, transactionId] - const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] - - return this._manager.monitorCharacteristicForDevice(...args) - } - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - async readDescriptorForService( - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId: ?TransactionId - ): Promise { - return this._manager.readDescriptorForDevice( - this.id, - serviceUUID, - characteristicUUID, - descriptorUUID, - transactionId - ) - } - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - async writeDescriptorForService( - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - return this._manager.writeDescriptorForDevice( - this.id, - serviceUUID, - characteristicUUID, - descriptorUUID, - valueBase64, - transactionId - ) - } -} diff --git a/src/Device.ts b/src/Device.ts new file mode 100644 index 000000000..8a08639a5 --- /dev/null +++ b/src/Device.ts @@ -0,0 +1,58 @@ +import type { BleManager, DeviceInfo, Subscription } from './BleManager' +import type { ConnectOptions, ConnectionPriority } from './types' + +export class Device { + readonly id: string + readonly name: string | null + readonly rssi: number + readonly mtu: number + readonly isConnectable: boolean | null + readonly serviceUuids: readonly string[] + readonly manufacturerData: string | null + + private manager: BleManager + + constructor(info: DeviceInfo, manager: BleManager) { + this.id = info.id + this.name = info.name + this.rssi = info.rssi + this.mtu = info.mtu + this.isConnectable = info.isConnectable + this.serviceUuids = info.serviceUuids + this.manufacturerData = info.manufacturerData + this.manager = manager + } + + async connect(options?: ConnectOptions | null): Promise { + const info = await this.manager.connectToDevice(this.id, options) + return new Device(info, this.manager) + } + + async cancelConnection(): Promise { + const info = await this.manager.cancelDeviceConnection(this.id) + return new Device(info, this.manager) + } + + async isConnected(): Promise { + return this.manager.isDeviceConnected(this.id) + } + + async discoverAllServicesAndCharacteristics(): Promise { + const info = await this.manager.discoverAllServicesAndCharacteristics(this.id) + return new Device(info, this.manager) + } + + async requestMTU(mtu: number): Promise { + const info = await this.manager.requestMTUForDevice(this.id, mtu) + return new Device(info, this.manager) + } + + async requestConnectionPriority(priority: ConnectionPriority): Promise { + const info = await this.manager.requestConnectionPriority(this.id, priority) + return new Device(info, this.manager) + } + + onDisconnected(callback: Parameters[1]): Subscription { + return this.manager.onDeviceDisconnected(this.id, callback) + } +} diff --git a/src/EventBatcher.ts b/src/EventBatcher.ts new file mode 100644 index 000000000..388d65d28 --- /dev/null +++ b/src/EventBatcher.ts @@ -0,0 +1,42 @@ +export class EventBatcher { + private buffer: T[] = [] + private timer: ReturnType | null = null + private disposed = false + + constructor( + private intervalMs: number, + private maxBatchSize: number, + private onBatch: (events: T[]) => void + ) { + if (intervalMs > 0) { + this.timer = setInterval(() => this.flush(), intervalMs) + } + } + + push(event: T): void { + if (this.disposed) return + if (this.intervalMs === 0) { + this.onBatch([event]) + return + } + this.buffer.push(event) + if (this.buffer.length >= this.maxBatchSize) { + this.flush() + } + } + + flush(): void { + if (this.buffer.length === 0) return + const batch = this.buffer.splice(0, this.maxBatchSize) + this.onBatch(batch) + } + + dispose(): void { + this.disposed = true + if (this.timer) { + clearInterval(this.timer) + this.timer = null + } + this.buffer.length = 0 + } +} diff --git a/src/Service.js b/src/Service.js deleted file mode 100644 index 29ba5a8d7..000000000 --- a/src/Service.js +++ /dev/null @@ -1,203 +0,0 @@ -// @flow -'use strict' - -import type { BleManager } from './BleManager' -import type { BleError } from './BleError' -import type { Characteristic } from './Characteristic' -import type { Descriptor } from './Descriptor' -import type { NativeService } from './BleModule' -import type { - DeviceId, - Identifier, - Base64, - UUID, - Subscription, - TransactionId, - CharacteristicSubscriptionType -} from './TypeDefinition' -import { isIOS } from './Utils' - -/** - * Service object. - */ -export class Service implements NativeService { - /** - * Internal BLE Manager handle - * @private - */ - _manager: BleManager - /** - * Service unique identifier - */ - id: Identifier - /** - * Service UUID - */ - uuid: UUID - /** - * Device's ID to which service belongs - */ - deviceID: DeviceId - /** - * Value indicating whether the type of service is primary or secondary. - */ - isPrimary: boolean - - /** - * Private constructor used to create {@link Service} object. - * - * @param {NativeService} nativeService NativeService properties to be copied. - * @param {BleManager} manager Current BleManager instance. - * @private - * @ignore - */ - constructor(nativeService: NativeService, manager: BleManager) { - Object.assign(this, nativeService) - Object.defineProperty(this, '_manager', { value: manager, enumerable: false }) - } - - /** - * {@link #blemanagercharacteristicsfordevice|bleManager.characteristicsForDevice()} with partially filled arguments. - * - * @returns {Promise>} Promise which emits array of {@link Characteristic} objects which are - * discovered for this service. - */ - characteristics(): Promise> { - return this._manager._characteristicsForService(this.id) - } - - /** - * {@link #blemanagerdescriptorsfordevice|bleManager.descriptorsForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for this {@link Service} in specified {@link Characteristic}. - */ - descriptorsForCharacteristic(characteristicUUID: UUID): Promise> { - return this._manager._descriptorsForService(this.id, characteristicUUID) - } - - /** - * {@link #blemanagerreadcharacteristicfordevice|bleManager.readCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID path. Latest value of {@link Characteristic} will be stored inside returned object. - */ - readCharacteristic(characteristicUUID: UUID, transactionId: ?TransactionId): Promise { - return this._manager._readCharacteristicForService(this.id, characteristicUUID, transactionId) - } - - /** - * {@link #blemanagerwritecharacteristicwithresponsefordevice|bleManager.writeCharacteristicWithResponseForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID path. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithResponse( - characteristicUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - return this._manager._writeCharacteristicWithResponseForService( - this.id, - characteristicUUID, - valueBase64, - transactionId - ) - } - - /** - * {@link #blemanagerwritecharacteristicwithoutresponsefordevice|bleManager.writeCharacteristicWithoutResponseForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID path. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithoutResponse( - characteristicUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - return this._manager._writeCharacteristicWithoutResponseForService( - this.id, - characteristicUUID, - valueBase64, - transactionId - ) - } - - /** - * {@link #blemanagermonitorcharacteristicfordevice|bleManager.monitorCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID - {@link Characteristic} UUID. - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitorCharacteristic( - characteristicUUID: UUID, - listener: (error: ?BleError, characteristic: ?Characteristic) => void, - transactionId: ?TransactionId, - subscriptionType: ?CharacteristicSubscriptionType - ): Subscription { - const commonArgs = [this.id, characteristicUUID, listener, transactionId] - const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] - - return this._manager._monitorCharacteristicForService(...args) - } - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - async readDescriptorForCharacteristic( - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId: ?TransactionId - ): Promise { - return this._manager._readDescriptorForService(this.id, characteristicUUID, descriptorUUID, transactionId) - } - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - async writeDescriptorForCharacteristic( - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId: ?TransactionId - ): Promise { - return this._manager._writeDescriptorForService( - this.id, - characteristicUUID, - descriptorUUID, - valueBase64, - transactionId - ) - } -} diff --git a/src/Service.ts b/src/Service.ts new file mode 100644 index 000000000..486d15141 --- /dev/null +++ b/src/Service.ts @@ -0,0 +1,9 @@ +export class Service { + readonly uuid: string + readonly deviceId: string + + constructor(uuid: string, deviceId: string) { + this.uuid = uuid + this.deviceId = deviceId + } +} diff --git a/src/TypeDefinition.js b/src/TypeDefinition.js deleted file mode 100644 index fb954bd09..000000000 --- a/src/TypeDefinition.js +++ /dev/null @@ -1,314 +0,0 @@ -// @flow -'use strict' - -import type { Device } from './Device' -import { BleErrorCode } from './BleError' - -/** - * Bluetooth device id. - */ -export type DeviceId = string - -/** - * Unique identifier for BLE objects. - */ -export type Identifier = number - -/** - * Bluetooth UUID - */ -export type UUID = string - -/** - * Base64 value - */ -export type Base64 = string - -/** - * Transaction identifier. All transaction identifiers in numeric form are reserved for internal use. - */ -export type TransactionId = string - -/** - * Characteritic subscription type. - */ -export type CharacteristicSubscriptionType = 'notification' | 'indication' - -/** - * [Android only] ConnectionOptions parameter to describe when to call BluetoothGatt.refresh() - */ -export type RefreshGattMoment = 'OnConnected' - -/** - * Subscription - * @interface - */ -export interface Subscription { - /** - * Removes subscription - * @memberof Subscription - * @ignore - */ - remove(): void; -} - -/** - * Type of error code mapping table - */ -export type BleErrorCodeMessageMapping = { [$Values]: string } - -/** - * Options which can be passed to when creating BLE Manager - */ -export interface BleManagerOptions { - /** - * BLE State restoration identifier used to restore state. - * @memberof BleManagerOptions - * @instance - */ - restoreStateIdentifier?: string; - - /** - * Optional function which is used to properly restore state of your BLE Manager. Callback - * is emitted in the beginning of BleManager creation and optional {@link BleRestoreState} - * is passed. When value is `null` application is launching for the first time, otherwise - * it contains saved state which may be used by developer to continue working with - * connected peripherals. - * @memberof BleManagerOptions - * @instance - */ - restoreStateFunction?: (restoredState: ?BleRestoredState) => void; - - /** - * Optional mapping of error codes to error messages. Uses {@link BleErrorCodeMessage} - * by default. - * - * To override logging UUIDs or MAC adresses in error messages copy the original object - * and overwrite values of interest to you. - * - * @memberof BleManagerOptions - * @instance - */ - errorCodesToMessagesMapping?: BleErrorCodeMessageMapping; -} - -/** - * Object representing information about restored BLE state after application relaunch. - */ -export interface BleRestoredState { - /** - * List of connected devices after state restoration. - * @type {Array} - * @instance - * @memberof BleRestoredState - */ - connectedPeripherals: Array; -} - -/** - * Scan mode for Bluetooth LE scan. - */ -export const ScanMode = { - /** - * A special Bluetooth LE scan mode. Applications using this scan mode will passively listen for - * other scan results without starting BLE scans themselves. - */ - Opportunistic: -1, - - /** - * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the - * least power. [default value] - */ - LowPower: 0, - - /** - * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that - * provides a good trade-off between scan frequency and power consumption. - */ - Balanced: 1, - - /** - * Scan using highest duty cycle. It's recommended to only use this mode when the application is - * running in the foreground. - */ - LowLatency: 2 -} - -/** - * Scan callback type for Bluetooth LE scan. - * @name ScanCallbackType - */ -export const ScanCallbackType = { - /** - * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria. - * If no filter is active, all advertisement packets are reported. [default value] - */ - AllMatches: 1, - - /** - * A result callback is only triggered for the first advertisement packet received that matches - * the filter criteria. - */ - FirstMatch: 2, - - /** - * Receive a callback when advertisements are no longer received from a device that has been - * previously reported by a first match callback. - */ - MatchLost: 4 -} - -/** - * Options which can be passed to scanning function - * @name ScanOptions - */ -export interface ScanOptions { - /** - * By allowing duplicates scanning records are received more frequently [iOS only] - * @memberof ScanOptions - * @instance - */ - allowDuplicates?: boolean; - - /** - * Scan mode for Bluetooth LE scan [Android only] - * @memberof ScanOptions - * @instance - */ - scanMode?: $Values; - - /** - * Scan callback type for Bluetooth LE scan [Android only] - * @memberof ScanOptions - * @instance - */ - callbackType?: $Values; - /** - * Use legacyScan (default true) [Android only] - * https://developer.android.com/reference/android/bluetooth/le/ScanSettings.Builder#setLegacy(boolean) - * @memberof ScanOptions - * @instance - */ - legacyScan?: boolean; -} - -/** - * Connection specific options to be passed before connection happen. [Not used] - */ -export interface ConnectionOptions { - /** - * Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote device - * becomes available (true). [Android only] - * @memberof ConnectionOptions - * @instance - */ - autoConnect?: boolean; - - /** - * Whether MTU size will be negotiated to this value. It is not guaranteed to get it after connection is successful. - * - * @memberof ConnectionOptions - * @instance - */ - requestMTU?: number; - - /** - * Whether action will be taken to reset services cache. This option may be useful when a peripheral's firmware was - * updated and it's services/characteristics were added/removed/altered. [Android only] - * {@link https://stackoverflow.com/questions/22596951/how-to-programmatically-force-bluetooth-low-energy-service-discovery-on-android} - * @memberof ConnectionOptions - * @instance - */ - refreshGatt?: RefreshGattMoment; - - /** - * Number of milliseconds after connection is automatically timed out. In case of race condition were connection is - * established right after timeout event, device will be disconnected immediately. Time out may happen earlier then - * specified due to OS specific behavior. - * - * @memberof ConnectionOptions - * @instance - */ - timeout?: number; -} - -/** - * Device Bluetooth Low Energy state. It's keys are used to check {@link #blemanagerstate} values - * received by {@link BleManager} - */ -export const State = { - /** - * The current state of the manager is unknown; an update is imminent. - */ - Unknown: 'Unknown', - /** - * The connection with the system service was momentarily lost; an update is imminent. - */ - Resetting: 'Resetting', - /** - * The platform does not support Bluetooth low energy. - */ - Unsupported: 'Unsupported', - /** - * The app is not authorized to use Bluetooth low energy. - */ - Unauthorized: 'Unauthorized', - /** - * Bluetooth is currently powered off. - */ - PoweredOff: 'PoweredOff', - /** - * Bluetooth is currently powered on and available to use. - */ - PoweredOn: 'PoweredOn' -} - -/** - * Native module logging log level. By default it is set to None. - * @name LogLevel - */ -export const LogLevel = { - /** - * Logging in native module is disabled - */ - None: 'None', - /** - * All logs in native module are shown - */ - Verbose: 'Verbose', - /** - * Only debug logs and of higher importance are shown in native module. - */ - Debug: 'Debug', - /** - * Only info logs and of higher importance are shown in native module. - */ - Info: 'Info', - /** - * Only warning logs and of higher importance are shown in native module. - */ - Warning: 'Warning', - /** - * Only error logs and of higher importance are shown in native module. - */ - Error: 'Error' -} - -/** - * Connection priority of BLE link determining the balance between power consumption and data throughput. - * @name ConnectionPriority - */ -export const ConnectionPriority = { - /** - * Default, recommended option balanced between power consumption and data throughput. - */ - Balanced: 0, - /** - * High priority, low latency connection, which increases transfer speed at the expense of power consumption. - */ - High: 1, - /** - * Low power, reduced data rate connection setup. - */ - LowPower: 2 -} diff --git a/src/Utils.js b/src/Utils.js deleted file mode 100644 index a7e1560ed..000000000 --- a/src/Utils.js +++ /dev/null @@ -1,29 +0,0 @@ -// @flow -'use strict' - -import { Platform } from 'react-native' -import type { UUID } from './TypeDefinition' - -/** - * Converts UUID to full 128bit, lowercase format which should be used to compare UUID values. - * - * @param {UUID} uuid 16bit, 32bit or 128bit UUID. - * @returns {UUID} 128bit lowercase UUID. - */ -export function fullUUID(uuid: UUID): UUID { - if (uuid.length === 4) { - return '0000' + uuid.toLowerCase() + '-0000-1000-8000-00805f9b34fb' - } - if (uuid.length === 8) { - return uuid.toLowerCase() + '-0000-1000-8000-00805f9b34fb' - } - return uuid.toLowerCase() -} - -export function fillStringWithArguments(value: string, object: Object): string { - return value.replace(/\{([^}]+)\}/g, function (_, arg: string) { - return object[arg] || '?' - }) -} - -export const isIOS = Platform.OS === 'ios' diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index d1ef75c9c..000000000 --- a/src/index.d.ts +++ /dev/null @@ -1,2084 +0,0 @@ -declare module 'react-native-ble-plx' { - // TypeDefinition.js ************************************************************************************************* - - /** - * [Android only] ConnectionOptions parameter to describe when to call BluetoothGatt.refresh() - */ - export type RefreshGattMoment = 'OnConnected' - /** - * Base64 value - */ - export type Base64 = string - /** - * Bluetooth UUID - */ - export type UUID = string - /** - * Unique identifier for BLE objects. - */ - export type Identifier = number - /** - * Bluetooth device id. - */ - export type DeviceId = string - /** - * Transaction identifier. All transaction identifiers in numeric form are reserved for internal use. - */ - export type TransactionId = string - - /** - * Characteritic subscription type. - */ - export type CharacteristicSubscriptionType = 'notification' | 'indication' - - /** - * Subscription - * @interface - */ - export interface Subscription { - /** - * Removes subscription - * @memberof Subscription - * @ignore - */ - remove(): void - } - - /** - * Type of error code mapping table - */ - export type BleErrorCodeMessageMapping = { [key in BleErrorCode]: string } - - /** - * Options which can be passed to when creating BLE Manager - */ - export interface BleManagerOptions { - /** - * BLE State restoration identifier used to restore state. - * @memberof BleManagerOptions - * @instance - */ - restoreStateIdentifier?: string - - /** - * Optional function which is used to properly restore state of your BLE Manager. Callback - * is emitted in the beginning of BleManager creation and optional {@link BleRestoreState} - * is passed. When value is `null` application is launching for the first time, otherwise - * it contains saved state which may be used by developer to continue working with - * connected peripherals. - * @memberof BleManagerOptions - * @instance - */ - restoreStateFunction?: (restoredState: BleRestoredState | null) => void - - /** - * Optional mapping of error codes to error messages. Uses {@link BleErrorCodeMessage} - * by default. - * - * To override logging UUIDs or MAC adresses in error messages copy the original object - * and overwrite values of interest to you. - * - * @memberof BleManagerOptions - * @instance - */ - errorCodesToMessagesMapping?: BleErrorCodeMessageMapping - } - - /** - * Object representing information about restored BLE state after application relaunch. - */ - export interface BleRestoredState { - /** - * List of connected devices after state restoration. - * @type {Array} - * @instance - * @memberof BleRestoredState - */ - connectedPeripherals: Device[] - } - - /** - * Scan mode for Bluetooth LE scan. - */ - export enum ScanMode { - /** - * A special Bluetooth LE scan mode. Applications using this scan mode will passively listen for - * other scan results without starting BLE scans themselves. - */ - Opportunistic = -1, - - /** - * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the - * least power. [default value] - */ - LowPower = 0, - - /** - * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that - * provides a good trade-off between scan frequency and power consumption. - */ - Balanced = 1, - - /** - * Scan using highest duty cycle. It's recommended to only use this mode when the application is - * running in the foreground. - */ - LowLatency = 2 - } - - /** - * Scan callback type for Bluetooth LE scan. - * @name ScanCallbackType - */ - export enum ScanCallbackType { - /** - * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria. - * If no filter is active, all advertisement packets are reported. [default value] - */ - AllMatches = 1, - - /** - * A result callback is only triggered for the first advertisement packet received that matches - * the filter criteria. - */ - FirstMatch = 2, - - /** - * Receive a callback when advertisements are no longer received from a device that has been - * previously reported by a first match callback. - */ - MatchLost = 4 - } - - /** - * Options which can be passed to scanning function - * @name ScanOptions - */ - export interface ScanOptions { - /** - * By allowing duplicates scanning records are received more frequently [iOS only] - */ - allowDuplicates?: boolean - /** - * Scan mode for Bluetooth LE scan [Android only] - */ - scanMode?: ScanMode - /** - * Scan callback type for Bluetooth LE scan [Android only] - */ - callbackType?: ScanCallbackType - /** - * Use legacyScan (default true) [Android only] - * https://developer.android.com/reference/android/bluetooth/le/ScanSettings.Builder#setLegacy(boolean) - */ - legacyScan?: boolean - } - - /** - * Connection specific options to be passed before connection happen. [Not used] - */ - export interface ConnectionOptions { - /** - * Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote device - * becomes available (true). [Android only] - * @memberof ConnectionOptions - * @instance - */ - autoConnect?: boolean - - /** - * Whether MTU size will be negotiated to this value. It is not guaranteed to get it after connection is successful. - * - * @memberof ConnectionOptions - * @instance - */ - requestMTU?: number - - /** - * Whether action will be taken to reset services cache. This option may be useful when a peripheral's firmware was - * updated and it's services/characteristics were added/removed/altered. [Android only] - * {@link https://stackoverflow.com/questions/22596951/how-to-programmatically-force-bluetooth-low-energy-service-discovery-on-android} - * @memberof ConnectionOptions - * @instance - */ - refreshGatt?: RefreshGattMoment - - /** - * Number of milliseconds after connection is automatically timed out. In case of race condition were connection is - * established right after timeout event, device will be disconnected immediately. Time out may happen earlier then - * specified due to OS specific behavior. - * - * @memberof ConnectionOptions - * @instance - */ - timeout?: number - } - - /** - * Device Bluetooth Low Energy state. It's keys are used to check {@link #blemanagerstate} values - * received by {@link BleManager} - */ - export enum State { - /** - * The current state of the manager is unknown; an update is imminent. - */ - Unknown = 'Unknown', - /** - * The connection with the system service was momentarily lost; an update is imminent. - */ - Resetting = 'Resetting', - /** - * The platform does not support Bluetooth low energy. - */ - Unsupported = 'Unsupported', - /** - * The app is not authorized to use Bluetooth low energy. - */ - Unauthorized = 'Unauthorized', - /** - * Bluetooth is currently powered off. - */ - PoweredOff = 'PoweredOff', - /** - * Bluetooth is currently powered on and available to use. - */ - PoweredOn = 'PoweredOn' - } - - /** - * Native module logging log level. By default it is set to None. - * @name LogLevel - */ - export enum LogLevel { - /** - * Logging in native module is disabled - */ - None = 'None', - /** - * All logs in native module are shown - */ - Verbose = 'Verbose', - /** - * Only debug logs and of higher importance are shown in native module. - */ - Debug = 'Debug', - /** - * Only info logs and of higher importance are shown in native module. - */ - Info = 'Info', - /** - * Only warning logs and of higher importance are shown in native module. - */ - Warning = 'Warning', - /** - * Only error logs and of higher importance are shown in native module. - */ - Error = 'Error' - } - - /** - * Connection priority of BLE link determining the balance between power consumption and data throughput. - * @name ConnectionPriority - */ - export enum ConnectionPriority { - /** - * Default, recommended option balanced between power consumption and data throughput. - */ - Balanced = 0, - /** - * High priority, low latency connection, which increases transfer speed at the expense of power consumption. - */ - High = 1, - /** - * Low power, reduced data rate connection setup. - */ - LowPower = 2 - } - - // Utils.js ********************************************************************************************************** - - /** - * Converts UUID to full 128bit, lowercase format which should be used to compare UUID values. - * - * @param {UUID} uuid 16bit, 32bit or 128bit UUID. - * @returns {UUID} 128bit lowercase UUID. - */ - export function fullUUID(uuid: UUID): UUID - - // BleError.js ******************************************************************************************************* - - export interface NativeBleError { - errorCode: BleErrorCode - attErrorCode: BleATTErrorCode | null - iosErrorCode: BleIOSErrorCode | null - androidErrorCode: BleAndroidErrorCode | null - reason: string | null - - deviceID?: string - serviceUUID?: string - characteristicUUID?: string - descriptorUUID?: string - internalMessage?: string - } - - /** - * BleError is an error class which is guaranteed to be thrown by all functions of this - * library. It contains additional properties which help to identify problems in - * platform independent way. - */ - export class BleError extends Error { - /** - * Platform independent error code. Possible values are defined in {@link BleErrorCode}. - */ - errorCode: BleErrorCode - - /** - * Platform independent error code related to ATT errors. - */ - attErrorCode: BleATTErrorCode | null - - /** - * iOS specific error code (if not an ATT error). - */ - iosErrorCode: BleIOSErrorCode | null - - /** - * Android specific error code (if not an ATT error). - */ - androidErrorCode: BleAndroidErrorCode | null - - /** - * Platform specific error message. - */ - reason: string | null - - constructor(nativeBleError: NativeBleError | string, errorMessageMapping: BleErrorCodeMessageMapping) - } - - /** - * Platform independent error code map adjusted to this library's use cases. - */ - export enum BleErrorCode { - // Implementation specific errors ---------------------------------------------------------------------------------- - /** - * This error can be thrown when unexpected error occurred and in most cases it is related to implementation bug. - * Original message is available in {@link #bleerrorreason|reason} property. - */ - UnknownError = 0, - /** - * Current promise failed to finish due to BleManager shutdown. It means that user called - * {@link #blemanagerdestroy|manager.destroy()} function before all operations completed. - */ - BluetoothManagerDestroyed = 1, - /** - * Promise was explicitly cancelled by a user with {@link #blemanagercanceltransaction|manager.cancelTransaction()} - * function call. - */ - OperationCancelled = 2, - /** - * Operation timed out and was cancelled. - */ - OperationTimedOut = 3, - /** - * Native module couldn't start operation due to internal state, which doesn't allow to do that. - */ - OperationStartFailed = 4, - /** - * Invalid UUIDs or IDs were passed to API call. - */ - InvalidIdentifiers = 5, - - // Bluetooth global states ----------------------------------------------------------------------------------------- - /** - * Bluetooth is not supported for this particular device. It may happen on iOS simulator for example. - */ - BluetoothUnsupported = 100, - /** - * There are no granted permissions which allow to use BLE functionality. On Android it may require - * android.permission.ACCESS_COARSE_LOCATION permission or android.permission.ACCESS_FINE_LOCATION permission. - */ - BluetoothUnauthorized = 101, - /** - * BLE is powered off on device. All previous operations and their state is invalidated. - */ - BluetoothPoweredOff = 102, - /** - * BLE stack is in unspecified state. - */ - BluetoothInUnknownState = 103, - /** - * BLE stack is resetting. - */ - BluetoothResetting = 104, - /** - * BLE state change failed. - */ - BluetoothStateChangeFailed = 105, - - // Peripheral errors. ---------------------------------------------------------------------------------------------- - /** - * Couldn't connect to specified device. - */ - DeviceConnectionFailed = 200, - /** - * Device was disconnected. - */ - DeviceDisconnected = 201, - /** - * Couldn't read RSSI from device. - */ - DeviceRSSIReadFailed = 202, - /** - * Device is already connected. It is not allowed to connect twice to the same device. - */ - DeviceAlreadyConnected = 203, - /** - * Couldn't find device with specified ID. - */ - DeviceNotFound = 204, - /** - * Operation failed because device has to be connected to perform it. - */ - DeviceNotConnected = 205, - /** - * Device could not change MTU value. - */ - DeviceMTUChangeFailed = 206, - - // Services -------------------------------------------------------------------------------------------------------- - /** - * Couldn't discover services for specified device. - */ - ServicesDiscoveryFailed = 300, - /** - * Couldn't discover included services for specified service. - */ - IncludedServicesDiscoveryFailed = 301, - /** - * Service with specified ID or UUID couldn't be found. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - ServiceNotFound = 302, - /** - * Services were not discovered. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - ServicesNotDiscovered = 303, - - // Characteristics ------------------------------------------------------------------------------------------------- - /** - * Couldn't discover characteristics for specified service. - */ - CharacteristicsDiscoveryFailed = 400, - /** - * Couldn't write to specified characteristic. Make sure that - * {@link #characteristiciswritablewithresponse|characteristic.isWritableWithResponse} - * or {@link #characteristiciswritablewithoutresponse|characteristic.isWritableWithoutResponse} is set to true. - */ - CharacteristicWriteFailed = 401, - /** - * Couldn't read from specified characteristic. Make sure that - * {@link #characteristicisreadable|characteristic.isReadable} is set to true. - */ - CharacteristicReadFailed = 402, - /** - * Couldn't set notification or indication for specified characteristic. Make sure that - * {@link #characteristicisnotifiable|characteristic.isNotifiable} or - * {@link #characteristicisindicatable|characteristic.isIndicatable} is set to true. - */ - CharacteristicNotifyChangeFailed = 403, - /** - * Characteristic with specified ID or UUID couldn't be found. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - CharacteristicNotFound = 404, - /** - * Characteristic were not discovered for specified service. User may need to call - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|manager.discoverAllServicesAndCharacteristicsForDevice} - * to cache them. - */ - CharacteristicsNotDiscovered = 405, - /** - * Invalid Base64 format was passed to characteristic API function call. - */ - CharacteristicInvalidDataFormat = 406, - - // Characteristics ------------------------------------------------------------------------------------------------- - /** - * Couldn't discover descriptor for specified characteristic. - */ - DescriptorsDiscoveryFailed = 500, - /** - * Couldn't write to specified descriptor. - */ - DescriptorWriteFailed = 501, - /** - * Couldn't read from specified descriptor. - */ - DescriptorReadFailed = 502, - /** - * Couldn't find specified descriptor. - */ - DescriptorNotFound = 503, - /** - * Descriptors are not discovered for specified characteristic. - */ - DescriptorsNotDiscovered = 504, - /** - * Invalid Base64 format was passed to descriptor API function call. - */ - DescriptorInvalidDataFormat = 505, - /** - * Issued a write to a descriptor, which is handled by OS. - */ - DescriptorWriteNotAllowed = 506, - - // Scanning errors ------------------------------------------------------------------------------------------------- - /** - * Cannot start scanning operation. - */ - ScanStartFailed = 600, - /** - * Location services are disabled. - */ - LocationServicesDisabled = 601 - } - - /** - * Error codes for ATT errors. - * @name BleATTErrorCode - */ - export enum BleATTErrorCode { - /** - * The ATT command or request successfully completed. - */ - Success = 0, - /** - * The attribute handle is invalid on this device. - */ - InvalidHandle = 1, - /** - * The attribute’s value cannot be read. - */ - ReadNotPermitted = 2, - /** - * The attribute’s value cannot be written. - */ - WriteNotPermitted = 3, - /** - * The attribute Protocol Data Unit (PDU) or “message” is invalid. - */ - InvalidPdu = 4, - /** - * The attribute requires authentication before its value can be read or written. - */ - InsufficientAuthentication = 5, - /** - * The attribute server does not support the request received by the client. - */ - RequestNotSupported = 6, - /** - * The specified offset value was past the end of the attribute’s value. - */ - InvalidOffset = 7, - /** - * The attribute requires authorization before its value can be read or written. - */ - InsufficientAuthorization = 8, - /** - * The prepare queue is full, because too many prepare write requests have been queued. - */ - PrepareQueueFull = 9, - /** - * The attribute is not found within the specified attribute handle range. - */ - AttributeNotFound = 10, - /** - * The attribute cannot be read or written using the ATT read blob request. - */ - AttributeNotLong = 11, - /** - * The encryption key size used for encrypting this link is insufficient. - */ - InsufficientEncryptionKeySize = 12, - /** - * The length of the attribute’s value is invalid for the intended operation. - */ - InvalidAttributeValueLength = 13, - /** - * The ATT request has encountered an unlikely error and therefore could not be completed. - */ - UnlikelyError = 14, - /** - * The attribute requires encryption before its value can be read or written. - */ - InsufficientEncryption = 15, - /** - * The attribute type is not a supported grouping attribute as defined by a higher-layer specification. - */ - UnsupportedGroupType = 16, - /** - * Resources are insufficient to complete the ATT request. - */ - InsufficientResources = 17 - - // Values 0x012 – 0x7F are reserved for future use. - } - - /** - * iOS specific error codes. - * @name BleIOSErrorCode - */ - export enum BleIOSErrorCode { - /** - * An unknown error occurred. - */ - Unknown = 0, - /** - * The specified parameters are invalid. - */ - InvalidParameters = 1, - /** - * The specified attribute handle is invalid. - */ - InvalidHandle = 2, - /** - * The device is not currently connected. - */ - NotConnected = 3, - /** - * The device has run out of space to complete the intended operation. - */ - OutOfSpace = 4, - /** - * The operation is canceled. - */ - OperationCancelled = 5, - /** - * The connection timed out. - */ - ConnectionTimeout = 6, - /** - * The peripheral disconnected. - */ - PeripheralDisconnected = 7, - /** - * The specified UUID is not permitted. - */ - UuidNotAllowed = 8, - /** - * The peripheral is already advertising. - */ - AlreadyAdvertising = 9, - /** - * The connection failed. - */ - ConnectionFailed = 10, - /** - * The device already has the maximum number of connections. - */ - ConnectionLimitReached = 11, - /** - * Unknown device. - */ - UnknownDevice = 12 - } - - /** - * Android specific error codes. - * @name BleAndroidErrorCode - */ - export enum BleAndroidErrorCode { - /** - * The device has insufficient resources to complete the intended operation. - */ - NoResources = 0x80, - /** - * Internal error occurred which may happen due to implementation error in BLE stack. - */ - InternalError = 0x81, - /** - * BLE stack implementation entered illegal state and operation couldn't complete. - */ - WrongState = 0x82, - /** - * BLE stack didn't allocate sufficient memory buffer for internal caches. - */ - DbFull = 0x83, - /** - * Maximum number of pending operations was exceeded. - */ - Busy = 0x84, - /** - * Generic BLE stack error. - */ - Error = 0x85, - /** - * Command is already queued up in GATT. - */ - CmdStarted = 0x86, - /** - * Illegal parameter was passed to internal BLE stack function. - */ - IllegalParameter = 0x87, - /** - * Operation is pending. - */ - Pending = 0x88, - /** - * Authorization failed before performing read or write operation. - */ - AuthFail = 0x89, - /** - * More cache entries were loaded then expected. - */ - More = 0x8a, - /** - * Invalid configuration - */ - InvalidCfg = 0x8b, - /** - * GATT service already started. - */ - ServiceStarted = 0x8c, - /** - * GATT link is encrypted but prone to man in the middle attacks. - */ - EncrypedNoMitm = 0x8d, - /** - * GATT link is not encrypted. - */ - NotEncrypted = 0x8e, - /** - * ATT command was sent but channel is congested. - */ - Congested = 0x8f - } - - // BleModule.js ****************************************************************************************************** - - /** - * Native device object passed from BleModule. - * @private - */ - export interface NativeDevice { - /** - * Device identifier: MAC address on Android and UUID on iOS. - * @private - */ - id: DeviceId - /** - * Device name if present - * @private - */ - name: string | null - /** - * Current Received Signal Strength Indication of device - * @private - */ - rssi: number | null - /** - * Current Maximum Transmission Unit for this device. When device is not connected - * default value of 23 is used. - * @private - */ - mtu: number - - // Advertisement - - /** - * Device's custom manufacturer data. Its format is defined by manufacturer. - * @private - */ - manufacturerData: Base64 | null - - /** - * Raw device scan data. When you have specific advertiser data, - * you can implement your own processing. - * @private - */ - rawScanRecord: Base64 - - /** - * Map od service UUIDs with associated data. - * @private - */ - serviceData: { [uuid: string]: Base64 } | null - - /** - * List of available services visible during scanning. - * @private - */ - serviceUUIDs: UUID[] | null - - /** - * User friendly name of device. - * @private - */ - localName: string | null - - /** - * Transmission power level of device. - * @private - */ - txPowerLevel: number | null - - /** - * List of solicited service UUIDs. - * @private - */ - solicitedServiceUUIDs: UUID[] | null - - /** - * Is device connectable. - * @private - */ - isConnectable: boolean | null - - /** - * List of overflow service UUIDs. - * @private - */ - overflowServiceUUIDs: UUID[] | null - } - - /** - * Native service object passed from BleModule. - * @private - */ - export interface NativeService { - /** - * Service unique identifier - * @private - */ - id: Identifier - /** - * Service UUID - * @private - */ - uuid: UUID - /** - * Device's ID to which service belongs - * @private - */ - deviceID: DeviceId - /** - * Value indicating whether the type of service is primary or secondary. - * @private - */ - isPrimary: boolean - } - - /** - * Native characteristic object passed from BleModule. - * @private - */ - export interface NativeCharacteristic { - /** - * Characteristic unique identifier - * @private - */ - id: Identifier - /** - * Characteristic UUID - * @private - */ - uuid: UUID - /** - * Service's ID to which characteristic belongs - * @private - */ - serviceID: Identifier - /** - * Service's UUID to which characteristic belongs - * @private - */ - serviceUUID: UUID - /** - * Device's ID to which characteristic belongs - * @private - */ - deviceID: DeviceId - /** - * True if characteristic can be read - * @private - */ - isReadable: boolean - /** - * True if characteristic can be written with response - * @private - */ - isWritableWithResponse: boolean - /** - * True if characteristic can be written without response - * @private - */ - isWritableWithoutResponse: boolean - /** - * True if characteristic can monitor value changes. - * @private - */ - isNotifiable: boolean - /** - * True if characteristic is monitoring value changes without ACK. - * @private - */ - isNotifying: boolean - /** - * True if characteristic is monitoring value changes with ACK. - * @private - */ - isIndicatable: boolean - /** - * Characteristic value if present - * @private - */ - value: Base64 | null - } - - /** - * Native descriptor object passed from BleModule. - * @private - */ - export interface NativeDescriptor { - /** - * Descriptor unique identifier - * @private - */ - id: Identifier - /** - * Descriptor UUID - * @private - */ - uuid: UUID - /** - * Characteristic's ID to which descriptor belongs - * @private - */ - characteristicID: Identifier - /** - * Characteristic's UUID to which descriptor belongs - * @private - */ - characteristicUUID: UUID - /** - * Service's ID to which descriptor belongs - * @private - */ - serviceID: Identifier - /** - * Service's UUID to which descriptor belongs - * @private - */ - serviceUUID: UUID - /** - * Device's ID to which descriptor belongs - * @private - */ - deviceID: DeviceId - /** - * Descriptor value if present - * @private - */ - value: Base64 | null - } - - /** - * Object representing information about restored BLE state after application relaunch. - * @private - */ - export interface NativeBleRestoredState { - /** - * List of connected devices after state restoration. - * @type {Array} - * @instance - * @memberof NativeBleRestoredState - * @private - */ - connectedPeripherals: NativeDevice[] - } - - // BleManager.js ***************************************************************************************************** - - /** - * - * BleManager is an entry point for react-native-ble-plx library. It provides all means to discover and work with - * {@link Device} instances. It should be initialized only once with `new` keyword and method - * {@link #blemanagerdestroy|destroy()} should be called on its instance when user wants to deallocate all resources. - * - * In case you want to properly support Background Mode, you should provide `restoreStateIdentifier` and - * `restoreStateFunction` in {@link BleManagerOptions}. - * - * @example - * const manager = new BleManager(); - * // ... work with BLE manager ... - * manager.destroy(); - */ - export class BleManager { - /** - * Creates an instance of {@link BleManager}. - */ - constructor(options?: BleManagerOptions) - - /** - * Destroys {@link BleManager} instance. A new instance needs to be created to continue working with - * this library. All operations which were in progress completes with - * @returns {Promise} - * {@link #bleerrorcodebluetoothmanagerdestroyed|BluetoothManagerDestroyed} error code. - */ - destroy(): Promise; - - // Mark: Common ---------------------------------------------------------------------------------------------------- - - /** - * Sets new log level for native module's logging mechanism. - * @param {LogLevel} logLevel New log level to be set. - * @returns {Promise} Current log level. - */ - setLogLevel(logLevel: LogLevel): Promise - - /** - * Get current log level for native module's logging mechanism. - * @returns {Promise} Current log level. - */ - logLevel(): Promise - - /** - * Cancels pending transaction. - * - * Few operations such as monitoring characteristic's value changes can be cancelled by a user. Basically every API - * entry which accepts `transactionId` allows to call `cancelTransaction` function. When cancelled operation is a - * promise or a callback which registers errors, {@link #bleerror|BleError} with error code - * {@link #bleerrorcodeoperationcancelled|OperationCancelled} will be emitted in that case. Cancelling transaction - * which doesn't exist is ignored. - * - * @example - * const transactionId = 'monitor_battery'; - * - * // Monitor battery notifications - * manager.monitorCharacteristicForDevice( - * device.id, '180F', '2A19', - * (error, characteristic) => { - * // Handle battery level changes... - * }, transactionId); - * - * // Cancel after specified amount of time - * setTimeout(() => manager.cancelTransaction(transactionId), 2000); - * - * @param {TransactionId} transactionId Id of pending transactions. - * @returns {Promise} - */ - cancelTransaction(transactionId: TransactionId): Promise - - // Mark: Monitoring state ------------------------------------------------------------------------------------------ - - /** - * Enable Bluetooth. This function blocks until BLE is in PoweredOn state. [Android only] - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise completes when state transition was successful. - */ - enable(transactionId?: TransactionId): Promise - - /** - * Disable Bluetooth. This function blocks until BLE is in PoweredOff state. [Android only] - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise completes when state transition was successful. - */ - disable(transactionId?: TransactionId): Promise - - /** - * Current, global {@link State} of a {@link BleManager}. All APIs are working only when active state - * is "PoweredOn". - * - * @returns {Promise} Promise which emits current state of BleManager. - */ - state(): Promise - - /** - * Notifies about {@link State} changes of a {@link BleManager}. - * - * @example - * const subscription = this.manager.onStateChange((state) => { - * if (state === 'PoweredOn') { - * this.scanAndConnect(); - * subscription.remove(); - * } - * }, true); - * - * @param {function(newState: State)} listener Callback which emits state changes of BLE Manager. - * Look at {@link State} for possible values. - * @param {boolean} [emitCurrentState=false] If true, current state will be emitted as well. Defaults to false. - * - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - onStateChange(listener: (newState: State) => void, emitCurrentState?: boolean): Subscription - - // Mark: Scanning -------------------------------------------------------------------------------------------------- - - /** - * Starts device scanning. When previous scan is in progress it will be stopped before executing this command. - * - * @param {?Array} UUIDs Array of strings containing {@link UUID}s of {@link Service}s which are registered in - * scanned {@link Device}. If `null` is passed, all available {@link Device}s will be scanned. - * @param {?ScanOptions} options Optional configuration for scanning operation. - * @param {function(error?: BleError, scannedDevice: ?Device)} listener Function which will be called for every scanned - * {@link Device} (devices may be scanned multiple times). It's first argument is potential {@link Error} which is set - * to non `null` value when scanning failed. You have to start scanning process again if that happens. Second argument - * is a scanned {@link Device}. - * @returns {Promise} the promise may be rejected if the operation is impossible to perform. - */ - startDeviceScan( - UUIDs: UUID[] | null, - options: ScanOptions | null, - listener: (error: BleError | null, scannedDevice: Device | null) => void - ): Promise - - /** - * Stops {@link Device} scan if in progress. - * @returns {Promise} the promise may be rejected if the operation is impossible to perform. - */ - stopDeviceScan(): Promise - - /** - * Request a connection parameter update. This functions may update connection parameters on Android API level 21 or - * above. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {ConnectionPriority} connectionPriority: Connection priority. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Connected device. - */ - requestConnectionPriorityForDevice( - deviceIdentifier: DeviceId, - connectionPriority: ConnectionPriority, - transactionId?: TransactionId - ): Promise - - /** - * Reads RSSI for connected device. - * - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Connected device with updated RSSI value. - */ - readRSSIForDevice(deviceIdentifier: DeviceId, transactionId?: TransactionId): Promise - - /** - * Request new MTU value for this device. This function currently is not doing anything - * on iOS platform as MTU exchange is done automatically. Since Android 14, - * mtu management has been changed, more information can be found at the link: - * https://developer.android.com/about/versions/14/behavior-changes-all#mtu-set-to-517 - * @param {DeviceId} deviceIdentifier Device identifier. - * @param {number} mtu New MTU to negotiate. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Device with updated MTU size. Default value is 23 (517 since Android 14). - */ - requestMTUForDevice(deviceIdentifier: DeviceId, mtu: number, transactionId?: TransactionId): Promise - - // Mark: Connection management ------------------------------------------------------------------------------------- - - /** - * Returns a list of known devices by their identifiers. - * @param {Array} deviceIdentifiers List of device identifiers. - * @returns {Promise>} List of known devices by their identifiers. - */ - devices(deviceIdentifiers: Array): Promise - - /** - * Returns a list of the peripherals (containing any of the specified services) currently connected to the system - * which have discovered services. Returned devices **may not be connected** to your application. Make sure to check - * if that's the case with function {@link #blemanagerisdeviceconnected|isDeviceConnected}. - * @param {Array} serviceUUIDs List of service UUIDs. Device must contain at least one of them to be listed. - * @returns {Promise>} List of known devices with discovered services as stated in the parameter. - */ - connectedDevices(serviceUUIDs: Array): Promise - - // Mark: Connection management ------------------------------------------------------------------------------------- - - /** - * Connects to {@link Device} with provided ID. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {?ConnectionOptions} options Platform specific options for connection establishment. - * @returns {Promise} Connected {@link Device} object if successful. - */ - connectToDevice(deviceIdentifier: DeviceId, options?: ConnectionOptions): Promise - - /** - * Disconnects from {@link Device} if it's connected or cancels pending connection. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier to be closed. - * @returns {Promise} Returns closed {@link Device} when operation is successful. - */ - cancelDeviceConnection(deviceIdentifier: DeviceId): Promise - - /** - * Monitors if {@link Device} was disconnected due to any errors or connection problems. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier to be monitored. - * @param {function(error?: BleError, device: ?Device)} listener - callback returning error as a reason of disconnection - * if available and {@link Device} object. If an error is null, that means the connection was terminated by - * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} call. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - onDeviceDisconnected( - deviceIdentifier: DeviceId, - listener: (error: BleError | null, device: Device | null) => void - ): Subscription - - /** - * Check connection state of a {@link Device}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @returns {Promise} Promise which emits `true` if device is connected, and `false` otherwise. - */ - isDeviceConnected(deviceIdentifier: DeviceId): Promise - - // Mark: Discovery ------------------------------------------------------------------------------------------------- - - /** - * Discovers all {@link Service}s, {@link Characteristic}s and {@link Descriptor}s for {@link Device}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise which emits {@link Device} object if all available services and - * characteristics have been discovered. - */ - discoverAllServicesAndCharacteristicsForDevice( - deviceIdentifier: DeviceId, - transactionId?: TransactionId - ): Promise - - // Mark: Service and characteristic getters ------------------------------------------------------------------------ - - /** - * List of discovered {@link Service}s for {@link Device}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @returns {Promise>} Promise which emits array of {@link Service} objects which are discovered for a - * {@link Device}. - */ - servicesForDevice(deviceIdentifier: DeviceId): Promise - - /** - * List of discovered {@link Characteristic}s for given {@link Device} and {@link Service}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @returns {Promise>} Promise which emits array of {@link Characteristic} objects which are - * discovered for a {@link Device} in specified {@link Service}. - */ - characteristicsForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID): Promise - - /** - * List of discovered {@link Descriptor}s for given {@link Device}, {@link Service} and {@link Characteristic}. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for a {@link Device}, {@link Service} in specified {@link Characteristic}. - */ - descriptorsForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID - ): Promise> - - // Mark: Characteristics operations -------------------------------------------------------------------------------- - - /** - * Read {@link Characteristic} value. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. - */ - readCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - transactionId?: TransactionId - ): Promise - - /** - * Write {@link Characteristic} value with response. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithResponseForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - base64Value: Base64, - transactionId?: TransactionId - ): Promise - - /** - * Write {@link Characteristic} value without response. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} base64Value Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithoutResponseForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - base64Value: Base64, - transactionId?: TransactionId - ): Promise - - /** - * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used - * in favour of indications. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {function(error?: BleError, characteristic?: Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitorCharacteristicForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - listener: (error: BleError | null, characteristic: Characteristic | null) => void, - transactionId?: TransactionId, - subscriptionType?: CharacteristicSubscriptionType - ): Subscription - - // Mark: Descriptors operations ---------------------------------------------------------------------------------- - - /** - * Read {@link Descriptor} value. - * - * @param {DeviceId} deviceIdentifier {@link Device} identifier. - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - readDescriptorForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId?: string - ): Promise - - /** - * Write {@link Descriptor} value. - * - * @param {DeviceId} deviceIdentifier Connected device identifier - * @param {UUID} serviceUUID Service UUID - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value - */ - writeDescriptorForDevice( - deviceIdentifier: DeviceId, - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId?: string - ): Promise - } - - // Device.js ********************************************************************************************************* - - /** - * Device instance which can be retrieved only by calling - * {@link #blemanagerstartdevicescan|bleManager.startDeviceScan()}. - */ - export class Device implements NativeDevice { - /** - * Device identifier: MAC address on Android and UUID on iOS. - */ - id: DeviceId - - /** - * Device name if present - */ - name: string | null - - /** - * Current Received Signal Strength Indication of device - */ - rssi: number | null - - /** - * Current Maximum Transmission Unit for this device. When device is not connected - * default value of 23 is used. - */ - mtu: number - - // Advertisement - - /** - * Device's custom manufacturer data. Its format is defined by manufacturer. - */ - manufacturerData: Base64 | null - - /** - * Raw device scan data. When you have specific advertiser data, - * you can implement your own processing. - */ - rawScanRecord: Base64 - - /** - * Map of service UUIDs (as keys) with associated data (as values). - */ - serviceData: { [uuid: string]: Base64 } | null - - /** - * List of available services visible during scanning. - */ - serviceUUIDs: UUID[] | null - - /** - * User friendly name of device. - */ - localName: string | null - - /** - * Transmission power level of device. - */ - txPowerLevel: number | null - - /** - * List of solicited service UUIDs. - */ - solicitedServiceUUIDs: UUID[] | null - - /** - * Is device connectable. [iOS only] - */ - isConnectable: boolean | null - - /** - * List of overflow service UUIDs. [iOS only] - */ - overflowServiceUUIDs: UUID[] | null - - /** - * Private constructor used to create {@link Device} object. - * - * @param {NativeDevice} nativeDevice Native device properties - * @param {BleManager} manager {@link BleManager} handle - * @private - */ - constructor(nativeDevice: NativeDevice, manager: BleManager) - - /** - * {@link #blemanagerrequestconnectionpriorityfordevice|bleManager.requestConnectionPriorityForDevice()} with partially filled arguments. - * - * @param {ConnectionPriority} connectionPriority: Connection priority. - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Connected device. - */ - requestConnectionPriority(connectionPriority: ConnectionPriority, transactionId?: TransactionId): Promise - - /** - * {@link #blemanagerreadrssifordevice|bleManager.readRSSIForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} This device with updated RSSI value. - */ - readRSSI(transactionId?: TransactionId): Promise - - /** - * {@link #blemanagerrequestmtufordevice|bleManager.requestMTUForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation. - * @returns {Promise} Device with updated MTU size. Default value is 23. - */ - requestMTU(mtu: number, transactionId?: TransactionId): Promise - - /** - * {@link #blemanagerconnecttodevice|bleManager.connectToDevice()} with partially filled arguments. - * - * @param {?ConnectionOptions} options Platform specific options for connection establishment. Not used currently. - * @returns {Promise} Connected {@link Device} object if successful. - */ - connect(options?: ConnectionOptions): Promise - - /** - * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} with partially filled arguments. - * - * @returns {Promise} Returns closed {@link Device} when operation is successful. - */ - cancelConnection(): Promise - - /** - * {@link #blemanagerisdeviceconnected|bleManager.isDeviceConnected()} with partially filled arguments. - * - * @returns {Promise} Promise which emits `true` if device is connected, and `false` otherwise. - */ - isConnected(): Promise - - /** - * {@link #blemanagerondevicedisconnected|bleManager.onDeviceDisconnected()} with partially filled arguments. - * - * @param {function(error: ?BleError, device: Device)} listener callback returning error as a reason of disconnection - * if available and {@link Device} object. If an error is null, that means the connection was terminated by - * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} call. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - onDisconnected(listener: (error: BleError | null, device: Device) => void): Subscription - - /** - * {@link #blemanagerdiscoverallservicesandcharacteristicsfordevice|bleManager.discoverAllServicesAndCharacteristicsForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Promise which emits {@link Device} object if all available services and - * characteristics have been discovered. - */ - discoverAllServicesAndCharacteristics(transactionId?: TransactionId): Promise - - /** - * {@link #blemanagerservicesfordevice|bleManager.servicesForDevice()} with partially filled arguments. - * - * @returns {Promise} Promise which emits array of {@link Service} objects which are discovered by this - * device. - */ - services(): Promise - - /** - * {@link #blemanagercharacteristicsfordevice|bleManager.characteristicsForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @returns {Promise} Promise which emits array of {@link Characteristic} objects which are - * discovered for a {@link Device} in specified {@link Service}. - */ - characteristicsForService(serviceUUID: string): Promise - - /** - * {@link #blemanagerdescriptorsfordevice|bleManager.descriptorsForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for this {@link Characteristic}. - */ - descriptorsForService(serviceUUID: UUID, characteristicUUID: UUID): Promise> - - /** - * {@link #blemanagerreadcharacteristicfordevice|bleManager.readCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. - */ - readCharacteristicForService( - serviceUUID: UUID, - characteristicUUID: UUID, - transactionId?: TransactionId - ): Promise - - /** - * {@link #blemanagerwritecharacteristicwithresponsefordevice|bleManager.writeCharacteristicWithResponseForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithResponseForService( - serviceUUID: UUID, - characteristicUUID: UUID, - valueBase64: Base64, - transactionId?: TransactionId - ): Promise - - /** - * {@link #blemanagerwritecharacteristicwithoutresponsefordevice|bleManager.writeCharacteristicWithoutResponseForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID paths. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithoutResponseForService( - serviceUUID: UUID, - characteristicUUID: UUID, - valueBase64: Base64, - transactionId?: TransactionId - ): Promise - - /** - * {@link #blemanagermonitorcharacteristicfordevice|bleManager.monitorCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitorCharacteristicForService( - serviceUUID: UUID, - characteristicUUID: UUID, - listener: (error: BleError | null, characteristic: Characteristic | null) => void, - transactionId?: TransactionId, - subscriptionType?: CharacteristicSubscriptionType - ): Subscription - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - readDescriptorForService( - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId?: string - ): Promise - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} serviceUUID {@link Service} UUID. - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - writeDescriptorForService( - serviceUUID: UUID, - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId?: string - ): Promise - } - - // Service.js ******************************************************************************************************** - - /** - * Service object. - */ - export class Service implements NativeService { - /** - * Service unique identifier - */ - id: Identifier - - /** - * Service UUID - */ - uuid: UUID - - /** - * Device's ID to which service belongs - */ - deviceID: DeviceId - - /** - * Value indicating whether the type of service is primary or secondary. - */ - isPrimary: boolean - - /** - * Private constructor used to create {@link Service} object. - * - * @param {NativeService} nativeService NativeService properties to be copied. - * @param {BleManager} manager Current BleManager instance. - * @private - * @ignore - */ - constructor(nativeService: NativeService, manager: BleManager) - - /** - * {@link #blemanagercharacteristicsfordevice|bleManager.characteristicsForDevice()} with partially filled arguments. - * - * @returns {Promise>} Promise which emits array of {@link Characteristic} objects which are - * discovered for this service. - */ - characteristics(): Promise - - /** - * {@link #blemanagerdescriptorsfordevice|bleManager.descriptorsForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for this {@link Service} in specified {@link Characteristic}. - */ - descriptorsForCharacteristic(characteristicUUID: UUID): Promise> - - /** - * {@link #blemanagerreadcharacteristicfordevice|bleManager.readCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID path. Latest value of {@link Characteristic} will be stored inside returned object. - */ - readCharacteristic(characteristicUUID: UUID, transactionId?: string): Promise - - /** - * {@link #blemanagerwritecharacteristicwithresponsefordevice|bleManager.writeCharacteristicWithResponseForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID path. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithResponse( - characteristicUUID: UUID, - valueBase64: Base64, - transactionId?: string - ): Promise - - /** - * {@link #blemanagerwritecharacteristicwithoutresponsefordevice|bleManager.writeCharacteristicWithoutResponseForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Characteristic} object matching specified - * UUID path. Latest value of characteristic may not be stored inside returned object. - */ - writeCharacteristicWithoutResponse( - characteristicUUID: UUID, - valueBase64: Base64, - transactionId?: string - ): Promise - - /** - * {@link #blemanagermonitorcharacteristicfordevice|bleManager.monitorCharacteristicForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID - {@link Characteristic} UUID. - * @param {function(error?: BleError, characteristic?: Characteristic)} listener callback which emits - * {@link Characteristic} objects with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitorCharacteristic( - characteristicUUID: UUID, - listener: (error: BleError | null, characteristic: Characteristic | null) => void, - transactionId?: string, - subscriptionType?: CharacteristicSubscriptionType - ): Subscription - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID {@link Characteristic} UUID. - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - readDescriptorForCharacteristic( - characteristicUUID: UUID, - descriptorUUID: UUID, - transactionId?: string - ): Promise - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} characteristicUUID Characteristic UUID - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - writeDescriptorForCharacteristic( - characteristicUUID: UUID, - descriptorUUID: UUID, - valueBase64: Base64, - transactionId?: string - ): Promise - } - - // Characteristic.js ************************************************************************************************* - - /** - * Characteristic object. - */ - export class Characteristic implements NativeCharacteristic { - /** - * Characteristic unique identifier - */ - id: Identifier - - /** - * Characteristic UUID - */ - uuid: UUID - - /** - * Service's ID to which characteristic belongs - */ - serviceID: Identifier - - /** - * Service's UUID to which characteristic belongs - */ - serviceUUID: UUID - - /** - * Device's ID to which characteristic belongs - */ - deviceID: DeviceId - - /** - * True if characteristic can be read - */ - isReadable: boolean - - /** - * True if characteristic can be written with response - */ - isWritableWithResponse: boolean - - /** - * True if characteristic can be written without response - */ - isWritableWithoutResponse: boolean - - /** - * True if characteristic can monitor value changes. - */ - isNotifiable: boolean - - /** - * True if characteristic is monitoring value changes without ACK. - */ - isNotifying: boolean - - /** - * True if characteristic is monitoring value changes with ACK. - */ - isIndicatable: boolean - - /** - * Characteristic value if present - */ - value: Base64 | null - - /** - * Private constructor used to create instance of {@link Characteristic}. - * @param {NativeCharacteristic} nativeCharacteristic NativeCharacteristic - * @param {BleManager} manager BleManager - * @private - */ - constructor(nativeCharacteristic: NativeCharacteristic, manager: BleManager) - - /** - * {@link #blemanagerdescriptorsfordevice|bleManager.descriptorsForDevice()} with partially filled arguments. - * - * @returns {Promise>} Promise which emits array of {@link Descriptor} objects which are - * discovered for this {@link Characteristic}. - */ - descriptors(): Promise> - - /** - * {@link #blemanagerreadcharacteristicfordevice|bleManager.readCharacteristicForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits this {@link Characteristic}. Latest value will be stored - * inside returned object. - */ - read(transactionId?: string): Promise - - /** - * {@link #blemanagerwritecharacteristicwithresponsefordevice|bleManager.writeCharacteristicWithResponseForDevice()} with partially filled arguments. - * - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits this {@link Characteristic}. Latest value may - * not be stored inside returned object. - */ - writeWithResponse(valueBase64: Base64, transactionId?: string): Promise - - /** - * {@link #blemanagerwritecharacteristicwithoutresponsefordevice|bleManager.writeCharacteristicWithoutResponseForDevice()} with partially filled arguments. - * - * @param {Base64} valueBase64 Value in Base64 format. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Promise} Promise which emits this {@link Characteristic}. Latest value may - * not be stored inside returned object. - */ - writeWithoutResponse(valueBase64: Base64, transactionId?: string): Promise - - /** - * {@link #blemanagermonitorcharacteristicfordevice|bleManager.monitorCharacteristicForDevice()} with partially filled arguments. - * - * @param {function(error?: BleError, characteristic?: Characteristic)} listener callback which emits - * this {@link Characteristic} with modified value for each notification. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic - * {@link #blemanagercanceltransaction|bleManager.cancelTransaction()} function. - * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. - */ - monitor( - listener: (error: BleError | null, characteristic: Characteristic | null) => void, - transactionId?: string, - subscriptionType?: CharacteristicSubscriptionType - ): Subscription - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} descriptorUUID {@link Descriptor} UUID. - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - readDescriptor(descriptorUUID: UUID, transactionId?: string): Promise - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {UUID} descriptorUUID Descriptor UUID - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - writeDescriptor(descriptorUUID: UUID, valueBase64: Base64, transactionId?: string): Promise - } - - // Descriptor.js ************************************************************************************************* - - /** - * Descriptor object. - */ - export class Descriptor implements NativeDescriptor { - /** - * Internal BLE Manager handle - * @private - */ - _manager: BleManager - - /** - * Descriptor unique identifier - */ - id: Identifier - - /** - * Descriptor UUID - */ - uuid: UUID - - /** - * Characteristic's ID to which descriptor belongs - */ - characteristicID: Identifier - - /** - * Characteristic's UUID to which descriptor belongs - */ - characteristicUUID: UUID - - /** - * Service's ID to which descriptor belongs - */ - serviceID: Identifier - - /** - * Service's UUID to which descriptor belongs - */ - serviceUUID: UUID - - /** - * Device's ID to which descriptor belongs - */ - deviceID: DeviceId - - /** - * Descriptor value if present - */ - value: Base64 | null - - /** - * Private constructor used to create instance of {@link Descriptor}. - * @param {NativeDescriptor} nativeDescriptor NativeDescriptor - * @param {BleManager} manager BleManager - * @private - */ - constructor(nativeDescriptor: NativeDescriptor, manager: BleManager) - - /** - * {@link #blemanagerreaddescriptorfordevice|bleManager.readDescriptorForDevice()} with partially filled arguments. - * - * @param {?TransactionId} transactionId optional `transactionId` which can be used in - * {@link #blemanagercanceltransaction|cancelTransaction()} function. - * @returns {Promise} Promise which emits first {@link Descriptor} object matching specified - * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. - */ - read(transactionId?: string): Promise - - /** - * {@link #blemanagerwritedescriptorfordevice|bleManager.writeDescriptorForDevice()} with partially filled arguments. - * - * @param {Base64} valueBase64 Value to be set coded in Base64 - * @param {?TransactionId} transactionId Transaction handle used to cancel operation - * @returns {Promise} Descriptor which saved passed value. - */ - write(valueBase64: Base64, transactionId?: string): Promise - } -} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 4d7205894..000000000 --- a/src/index.js +++ /dev/null @@ -1,20 +0,0 @@ -export { BleError, BleErrorCode, BleAndroidErrorCode, BleIOSErrorCode, BleATTErrorCode } from './BleError' -export { BleManager } from './BleManager' -export { Device } from './Device' -export { Service } from './Service' -export { Characteristic } from './Characteristic' -export { Descriptor } from './Descriptor' -export { fullUUID } from './Utils' -export { State, LogLevel, ConnectionPriority, ScanCallbackType, ScanMode } from './TypeDefinition' - -export type { - Subscription, - DeviceId, - UUID, - TransactionId, - Base64, - ScanOptions, - ConnectionOptions, - BleManagerOptions, - BleRestoredState -} from './TypeDefinition' diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..d411c2260 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,23 @@ +export { BleManager } from './BleManager' +export type { + BleManagerOptions, + Subscription, + MonitorOptions, + DeviceInfo, + CharacteristicInfo, + ScanResult, + ConnectionStateEvent, + CharacteristicValueEvent, + BondStateEvent, + ConnectionEvent, + RestoreStateEvent, + L2CAPChannelEvent, + PhyInfo +} from './BleManager' +export { Device } from './Device' +export { Service } from './Service' +export { Characteristic } from './Characteristic' +export { Descriptor } from './Descriptor' +export { BleError, BleErrorCode } from './BleError' +export { State, LogLevel, ConnectionPriority, ConnectionState } from './types' +export type { ScanOptions, ConnectOptions } from './types' diff --git a/src/specs/NativeBlePlx.ts b/src/specs/NativeBlePlx.ts new file mode 100644 index 000000000..a4799ba05 --- /dev/null +++ b/src/specs/NativeBlePlx.ts @@ -0,0 +1,175 @@ +import type { TurboModule, CodegenTypes } from 'react-native' +import { TurboModuleRegistry } from 'react-native' + +// All types must be inline — Codegen ignores imports from other files +// Union types NOT supported — use string with runtime validation + +export type DeviceInfo = Readonly<{ + id: string // Android: MAC address, iOS: opaque UUID + name: string | null + rssi: number + mtu: number + isConnectable: boolean | null + serviceUuids: ReadonlyArray + manufacturerData: string | null // Base64-encoded +}> + +export type CharacteristicInfo = Readonly<{ + deviceId: string + serviceUuid: string + uuid: string + value: string | null // Base64-encoded + isNotifying: boolean + isIndicatable: boolean + isReadable: boolean + isWritableWithResponse: boolean + isWritableWithoutResponse: boolean +}> + +export type ScanResult = Readonly<{ + id: string + name: string | null + rssi: number + serviceUuids: ReadonlyArray + manufacturerData: string | null +}> + +export type ConnectionStateEvent = Readonly<{ + deviceId: string + state: string // 'connecting' | 'connected' | 'disconnecting' | 'disconnected' + errorCode: number | null + errorMessage: string | null +}> + +export type CharacteristicValueEvent = Readonly<{ + deviceId: string + serviceUuid: string + characteristicUuid: string + value: string // Base64-encoded + transactionId: string | null +}> + +export type StateChangeEvent = Readonly<{ + state: string // 'Unknown' | 'Resetting' | 'Unsupported' | 'Unauthorized' | 'PoweredOff' | 'PoweredOn' +}> + +export type RestoreStateEvent = Readonly<{ + devices: ReadonlyArray +}> + +export type BleErrorInfo = Readonly<{ + code: number + message: string + isRetryable: boolean + deviceId: string | null + serviceUuid: string | null + characteristicUuid: string | null + operation: string | null + platform: string + nativeDomain: string | null + nativeCode: number | null + gattStatus: number | null + attErrorCode: number | null +}> + +export interface Spec extends TurboModule { + // Lifecycle + createClient(restoreStateIdentifier: string | null): Promise + destroyClient(): Promise + + // State + state(): Promise + + // Scanning + startDeviceScan( + uuids: ReadonlyArray | null, + options: Readonly<{ + scanMode?: number + callbackType?: number + legacyScan?: boolean + allowDuplicates?: boolean + }> | null + ): void + stopDeviceScan(): Promise + + // Connection + connectToDevice( + deviceId: string, + options: Readonly<{ + autoConnect?: boolean + timeout?: number + retries?: number + retryDelay?: number + requestMtu?: number + }> | null + ): Promise + cancelDeviceConnection(deviceId: string): Promise + isDeviceConnected(deviceId: string): Promise + + // Discovery + discoverAllServicesAndCharacteristics(deviceId: string, transactionId: string | null): Promise + + // Read/Write + readCharacteristic( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + transactionId: string | null + ): Promise + writeCharacteristic( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + value: string, + withResponse: boolean, + transactionId: string | null + ): Promise + + // Monitor + monitorCharacteristic( + deviceId: string, + serviceUuid: string, + characteristicUuid: string, + subscriptionType: string | null, + transactionId: string | null + ): void + + // MTU + getMtu(deviceId: string): Promise + requestMtu(deviceId: string, mtu: number, transactionId: string | null): Promise + + // PHY + requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise + readPhy(deviceId: string): Promise + + // Connection Priority + requestConnectionPriority(deviceId: string, priority: number): Promise + + // L2CAP + openL2CAPChannel(deviceId: string, psm: number): Promise> + writeL2CAPChannel(channelId: number, data: string): Promise + closeL2CAPChannel(channelId: number): Promise + + // Bonding + getBondedDevices(): Promise> + + // Authorization + getAuthorizationStatus(): Promise + + // Cancellation + cancelTransaction(transactionId: string): Promise + + // Typed Event Emitters + readonly onScanResult: CodegenTypes.EventEmitter + readonly onConnectionStateChange: CodegenTypes.EventEmitter + readonly onCharacteristicValueUpdate: CodegenTypes.EventEmitter + readonly onStateChange: CodegenTypes.EventEmitter + readonly onRestoreState: CodegenTypes.EventEmitter + readonly onError: CodegenTypes.EventEmitter + readonly onBondStateChange: CodegenTypes.EventEmitter> + readonly onConnectionEvent: CodegenTypes.EventEmitter> + readonly onL2CAPData: CodegenTypes.EventEmitter> + readonly onL2CAPClose: CodegenTypes.EventEmitter> +} + +export default TurboModuleRegistry.get('NativeBlePlx') diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..2792c14d7 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,51 @@ +// src/types.ts — Public types using const objects with as const + +export const State = { + Unknown: 'Unknown', + Resetting: 'Resetting', + Unsupported: 'Unsupported', + Unauthorized: 'Unauthorized', + PoweredOff: 'PoweredOff', + PoweredOn: 'PoweredOn' +} as const +export type State = (typeof State)[keyof typeof State] + +export const LogLevel = { + None: 'None', + Verbose: 'Verbose', + Debug: 'Debug', + Info: 'Info', + Warning: 'Warning', + Error: 'Error' +} as const +export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel] + +export const ConnectionPriority = { + Balanced: 0, + High: 1, + LowPower: 2 +} as const +export type ConnectionPriority = (typeof ConnectionPriority)[keyof typeof ConnectionPriority] + +export const ConnectionState = { + Disconnected: 'disconnected', + Connecting: 'connecting', + Connected: 'connected', + Disconnecting: 'disconnecting' +} as const +export type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState] + +export interface ScanOptions { + scanMode?: number + callbackType?: number + legacyScan?: boolean + allowDuplicates?: boolean +} + +export interface ConnectOptions { + autoConnect?: boolean + timeout?: number + retries?: number + retryDelay?: number + requestMtu?: number +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest.xcodeproj/project.pbxproj b/test-peripheral-app-ios/BlePlxTest/BlePlxTest.xcodeproj/project.pbxproj new file mode 100644 index 000000000..db956cae5 --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest.xcodeproj/project.pbxproj @@ -0,0 +1,342 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + A1000001 /* BlePlxTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2000001 /* BlePlxTestApp.swift */; }; + A1000002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2000002 /* ContentView.swift */; }; + A1000003 /* BLEPeripheralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2000003 /* BLEPeripheralManager.swift */; }; + A1000004 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A2000004 /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A2000001 /* BlePlxTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlePlxTestApp.swift; sourceTree = ""; }; + A2000002 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + A2000003 /* BLEPeripheralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEPeripheralManager.swift; sourceTree = ""; }; + A2000004 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A2000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A2000010 /* BlePlxTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlePlxTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A3000001 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A4000001 = { + isa = PBXGroup; + children = ( + A4000002 /* BlePlxTest */, + A4000003 /* Products */, + ); + sourceTree = ""; + }; + A4000002 /* BlePlxTest */ = { + isa = PBXGroup; + children = ( + A2000001 /* BlePlxTestApp.swift */, + A2000002 /* ContentView.swift */, + A2000003 /* BLEPeripheralManager.swift */, + A2000004 /* Assets.xcassets */, + A2000005 /* Info.plist */, + ); + path = BlePlxTest; + sourceTree = ""; + }; + A4000003 /* Products */ = { + isa = PBXGroup; + children = ( + A2000010 /* BlePlxTest.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A5000001 /* BlePlxTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7000003 /* Build configuration list for PBXNativeTarget "BlePlxTest" */; + buildPhases = ( + A6000001 /* Sources */, + A3000001 /* Frameworks */, + A6000002 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BlePlxTest; + productName = BlePlxTest; + productReference = A2000010 /* BlePlxTest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A8000001 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + A5000001 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = A7000001 /* Build configuration list for PBXProject "BlePlxTest" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A4000001; + productRefGroup = A4000003 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A5000001 /* BlePlxTest */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A6000002 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1000004 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A6000001 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1000001 /* BlePlxTestApp.swift in Sources */, + A1000002 /* ContentView.swift in Sources */, + A1000003 /* BLEPeripheralManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A9000001 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A9000002 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A9000003 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2974F4A5QH; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BlePlxTest/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BlePlxTest; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bleplx.testperipheral.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A9000004 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2974F4A5QH; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BlePlxTest/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BlePlxTest; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bleplx.testperipheral.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A7000001 /* Build configuration list for PBXProject "BlePlxTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A9000001 /* Debug */, + A9000002 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A7000003 /* Build configuration list for PBXNativeTarget "BlePlxTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A9000003 /* Debug */, + A9000004 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + + }; + rootObject = A8000001 /* Project object */; +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AccentColor.colorset/Contents.json b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AppIcon.appiconset/Contents.json b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/Contents.json b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/BLEPeripheralManager.swift b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/BLEPeripheralManager.swift new file mode 100644 index 000000000..d99b59feb --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/BLEPeripheralManager.swift @@ -0,0 +1,484 @@ +import Foundation +import CoreBluetooth + +final class BLEPeripheralManager: NSObject, ObservableObject { + + // MARK: - UUIDs (matching Android) + + static let serviceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789abc") + static let readCounterUUID = CBUUID(string: "12345678-1234-1234-1234-123456789a01") + static let writeEchoUUID = CBUUID(string: "12345678-1234-1234-1234-123456789a02") + static let notifyStreamUUID = CBUUID(string: "12345678-1234-1234-1234-123456789a03") + static let indicateStreamUUID = CBUUID(string: "12345678-1234-1234-1234-123456789a04") + static let mtuTestUUID = CBUUID(string: "12345678-1234-1234-1234-123456789a05") + static let writeNoRespUUID = CBUUID(string: "12345678-1234-1234-1234-123456789A06") + static let l2capPsmUUID = CBUUID(string: "12345678-1234-1234-1234-123456789A07") + + // MARK: - Published state + + @Published var statusText: String = "Initializing..." + @Published var connectionText: String = "No connections" + @Published var logLines: [String] = [] + @Published var peripheralIdentifier: String = "" + + // MARK: - Private state + + private var peripheralManager: CBPeripheralManager! + private var service: CBMutableService? + + private var notifyCharacteristic: CBMutableCharacteristic? + private var indicateCharacteristic: CBMutableCharacteristic? + + private var connectedCentral: CBCentral? + private var subscribedNotifyCentral: CBCentral? + private var subscribedIndicateCentral: CBCentral? + + private var readCounter: UInt32 = 0 + private var echoData = Data() + private var noResponseData = Data() + + private var l2capPSM: UInt16 = 0 + private var l2capChannel: CBL2CAPChannel? + private var l2capPsmCharacteristic: CBMutableCharacteristic? + + private var notifyCounter: UInt32 = 0 + private var indicateCounter: UInt32 = 0 + private var notifyEnabled = false + private var indicateEnabled = false + + private var notifyTimer: Timer? + private var indicateTimer: Timer? + + private var notifyBackpressure = false + private var indicateBackpressure = false + + private var negotiatedMtu: UInt16 = 23 + + // MARK: - Init + + override init() { + super.init() + peripheralManager = CBPeripheralManager(delegate: self, queue: nil) + } + + deinit { + stopTimers() + peripheralManager.stopAdvertising() + } + + // MARK: - Helpers + + private func appendLog(_ message: String) { + DispatchQueue.main.async { + self.logLines.append(message) + if self.logLines.count > 200 { + self.logLines.removeFirst(self.logLines.count - 200) + } + } + } + + private func shortUUID(_ uuid: CBUUID) -> String { + let s = uuid.uuidString + return String(s.suffix(4)) + } + + private func uint32LEData(_ value: UInt32) -> Data { + var v = value.littleEndian + return Data(bytes: &v, count: 4) + } + + private func uint16LEData(_ value: UInt16) -> Data { + var v = value.littleEndian + return Data(bytes: &v, count: 2) + } + + // MARK: - Service setup + + private func setupService() { + let readCounterChar = CBMutableCharacteristic( + type: Self.readCounterUUID, + properties: [.read], + value: nil, // nil = dynamic value via delegate + permissions: [.readable] + ) + + let writeEchoChar = CBMutableCharacteristic( + type: Self.writeEchoUUID, + properties: [.read, .write], + value: nil, + permissions: [.readable, .writeable] + ) + + let notifyChar = CBMutableCharacteristic( + type: Self.notifyStreamUUID, + properties: [.read, .notify], + value: nil, + permissions: [.readable] + ) + + let indicateChar = CBMutableCharacteristic( + type: Self.indicateStreamUUID, + properties: [.read, .indicate], + value: nil, + permissions: [.readable] + ) + + let mtuChar = CBMutableCharacteristic( + type: Self.mtuTestUUID, + properties: [.read], + value: nil, + permissions: [.readable] + ) + + let writeNoRespChar = CBMutableCharacteristic( + type: Self.writeNoRespUUID, + properties: [.read, .writeWithoutResponse], + value: nil, + permissions: [.readable, .writeable] + ) + + let l2capPsmChar = CBMutableCharacteristic( + type: Self.l2capPsmUUID, + properties: [.read], + value: nil, + permissions: [.readable] + ) + + notifyCharacteristic = notifyChar + indicateCharacteristic = indicateChar + l2capPsmCharacteristic = l2capPsmChar + + let svc = CBMutableService(type: Self.serviceUUID, primary: true) + svc.characteristics = [readCounterChar, writeEchoChar, notifyChar, indicateChar, mtuChar, writeNoRespChar, l2capPsmChar] + service = svc + + peripheralManager.add(svc) + appendLog("GATT server opened with test service") + } + + private func publishL2CAPChannel() { + peripheralManager.publishL2CAPChannel(withEncryption: false) + appendLog("L2CAP: publishing channel (no encryption)") + } + + private func startAdvertising() { + peripheralManager.startAdvertising([ + CBAdvertisementDataLocalNameKey: "BlePlxTest", + CBAdvertisementDataServiceUUIDsKey: [Self.serviceUUID] + ]) + } + + // MARK: - Timers + + private func startNotifyTimer() { + notifyTimer?.invalidate() + notifyCounter = 0 + notifyTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in + self?.sendNotification() + } + } + + private func stopNotifyTimer() { + notifyTimer?.invalidate() + notifyTimer = nil + } + + private func startIndicateTimer() { + indicateTimer?.invalidate() + indicateCounter = 0 + indicateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in + self?.sendIndication() + } + } + + private func stopIndicateTimer() { + indicateTimer?.invalidate() + indicateTimer = nil + } + + private func stopTimers() { + stopNotifyTimer() + stopIndicateTimer() + } + + // MARK: - Send notifications / indications + + private func sendNotification() { + guard let char = notifyCharacteristic, let central = subscribedNotifyCentral else { return } + guard !notifyBackpressure else { return } + notifyCounter += 1 + let data = uint32LEData(notifyCounter) + let sent = peripheralManager.updateValue(data, for: char, onSubscribedCentrals: [central]) + if !sent { + notifyCounter -= 1 + notifyBackpressure = true + appendLog("NOTIFY backpressure: transmit queue full, pausing until ready") + } + } + + private func sendIndication() { + guard let char = indicateCharacteristic, let central = subscribedIndicateCentral else { return } + guard !indicateBackpressure else { return } + indicateCounter += 1 + let data = uint32LEData(indicateCounter) + let sent = peripheralManager.updateValue(data, for: char, onSubscribedCentrals: [central]) + if !sent { + indicateCounter -= 1 + indicateBackpressure = true + appendLog("INDICATE backpressure: transmit queue full, pausing until ready") + } + } +} + +// MARK: - CBPeripheralManagerDelegate + +extension BLEPeripheralManager: CBPeripheralManagerDelegate { + + func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { + DispatchQueue.main.async { + switch peripheral.state { + case .poweredOn: + self.statusText = "Bluetooth powered on" + self.appendLog("Bluetooth powered on") + self.setupService() + self.startAdvertising() + self.publishL2CAPChannel() + case .poweredOff: + self.statusText = "Bluetooth is off" + self.appendLog("Bluetooth powered off") + case .unauthorized: + self.statusText = "Bluetooth unauthorized" + self.appendLog("Bluetooth unauthorized") + case .unsupported: + self.statusText = "BLE not supported" + self.appendLog("BLE not supported on this device") + case .resetting: + self.statusText = "Bluetooth resetting..." + self.appendLog("Bluetooth resetting") + case .unknown: + self.statusText = "Bluetooth state unknown" + @unknown default: + self.statusText = "Bluetooth state unknown" + } + } + } + + func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) { + DispatchQueue.main.async { + if let error = error { + self.statusText = "Advertising failed: \(error.localizedDescription)" + self.appendLog("Advertising failed: \(error.localizedDescription)") + } else { + self.statusText = "Advertising..." + self.appendLog("Advertising started as 'BlePlxTest'") + } + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) { + if let error = error { + appendLog("Failed to add service: \(error.localizedDescription)") + } else { + appendLog("Service added: \(shortUUID(service.uuid))") + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { + DispatchQueue.main.async { + self.connectedCentral = central + self.negotiatedMtu = UInt16(central.maximumUpdateValueLength + 3) // ATT header is 3 bytes + self.connectionText = "Connected: \(central.identifier.uuidString.prefix(8))..." + + if characteristic.uuid == Self.notifyStreamUUID { + self.subscribedNotifyCentral = central + self.notifyEnabled = true + self.startNotifyTimer() + self.appendLog("NOTIFY enabled") + } else if characteristic.uuid == Self.indicateStreamUUID { + self.subscribedIndicateCentral = central + self.indicateEnabled = true + self.startIndicateTimer() + self.appendLog("INDICATE enabled") + } + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { + DispatchQueue.main.async { + if characteristic.uuid == Self.notifyStreamUUID { + self.notifyEnabled = false + self.subscribedNotifyCentral = nil + self.stopNotifyTimer() + self.appendLog("NOTIFY disabled") + } else if characteristic.uuid == Self.indicateStreamUUID { + self.indicateEnabled = false + self.subscribedIndicateCentral = nil + self.stopIndicateTimer() + self.appendLog("INDICATE disabled") + } + + // Check if anything is still subscribed + if self.subscribedNotifyCentral == nil && self.subscribedIndicateCentral == nil { + self.connectedCentral = nil + self.connectionText = "No connections" + } + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { + let uuid = request.characteristic.uuid + var value: Data + + switch uuid { + case Self.readCounterUUID: + readCounter += 1 + value = uint32LEData(readCounter) + case Self.writeEchoUUID: + value = echoData + case Self.notifyStreamUUID: + value = uint32LEData(notifyCounter) + case Self.indicateStreamUUID: + value = uint32LEData(indicateCounter) + case Self.mtuTestUUID: + if let central = request.central as CBCentral? { + negotiatedMtu = UInt16(central.maximumUpdateValueLength + 3) + } + value = uint16LEData(negotiatedMtu) + case Self.writeNoRespUUID: + value = noResponseData + case Self.l2capPsmUUID: + value = uint16LEData(l2capPSM) + default: + peripheral.respond(to: request, withResult: .requestNotSupported) + return + } + + if request.offset > value.count { + peripheral.respond(to: request, withResult: .invalidOffset) + return + } + + request.value = value.subdata(in: request.offset.. \(Array(value))") + + // Track connection + DispatchQueue.main.async { + if self.connectedCentral == nil { + self.connectedCentral = request.central + self.connectionText = "Connected: \(request.central.identifier.uuidString.prefix(8))..." + self.appendLog("Device connected: \(request.central.identifier.uuidString.prefix(8))...") + } + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { + for request in requests { + if request.characteristic.uuid == Self.writeEchoUUID { + if let value = request.value { + echoData = value + appendLog("WRITE \(shortUUID(request.characteristic.uuid)) <- \(Array(value))") + } + } else if request.characteristic.uuid == Self.writeNoRespUUID { + if let value = request.value { + noResponseData = value + appendLog("WRITE_NO_RESP \(shortUUID(request.characteristic.uuid)) <- \(Array(value))") + } + } + + // Track connection + DispatchQueue.main.async { + if self.connectedCentral == nil { + self.connectedCentral = request.central + self.connectionText = "Connected: \(request.central.identifier.uuidString.prefix(8))..." + self.appendLog("Device connected: \(request.central.identifier.uuidString.prefix(8))...") + } + } + } + + peripheral.respond(to: requests[0], withResult: .success) + } + + func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) { + // CoreBluetooth signals the transmit queue has space again + if notifyBackpressure { + notifyBackpressure = false + appendLog("NOTIFY backpressure relieved, resuming") + sendNotification() + } + if indicateBackpressure { + indicateBackpressure = false + appendLog("INDICATE backpressure relieved, resuming") + sendIndication() + } + } + + // MARK: - L2CAP + + func peripheralManager(_ peripheral: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) { + if let error = error { + appendLog("L2CAP: failed to publish channel: \(error.localizedDescription)") + return + } + l2capPSM = UInt16(PSM) + appendLog("L2CAP: published channel with PSM \(PSM)") + } + + func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?) { + if let error = error { + appendLog("L2CAP: connection error: \(error.localizedDescription)") + return + } + guard let channel = channel else { + appendLog("L2CAP: didOpen called with nil channel") + return + } + l2capChannel = channel + appendLog("L2CAP: incoming connection opened (PSM \(channel.psm))") + + channel.inputStream.delegate = self + channel.outputStream.delegate = self + channel.inputStream.schedule(in: .main, forMode: .default) + channel.outputStream.schedule(in: .main, forMode: .default) + channel.inputStream.open() + channel.outputStream.open() + } +} + +// MARK: - StreamDelegate (L2CAP echo) + +extension BLEPeripheralManager: StreamDelegate { + + func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + switch eventCode { + case .hasBytesAvailable: + guard let inputStream = aStream as? InputStream else { return } + var buffer = [UInt8](repeating: 0, count: 1024) + let bytesRead = inputStream.read(&buffer, maxLength: buffer.count) + if bytesRead > 0 { + let data = Data(buffer[0.. Int in + guard let baseAddress = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return 0 } + return outputStream.write(baseAddress, maxLength: bytesRead) + } + appendLog("L2CAP: echoed \(written) bytes") + } + } + case .hasSpaceAvailable: + break + case .errorOccurred: + appendLog("L2CAP: stream error: \(aStream.streamError?.localizedDescription ?? "unknown")") + case .endEncountered: + appendLog("L2CAP: stream ended") + aStream.close() + aStream.remove(from: .main, forMode: .default) + case .openCompleted: + appendLog("L2CAP: stream open completed") + default: + break + } + } +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/BlePlxTestApp.swift b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/BlePlxTestApp.swift new file mode 100644 index 000000000..ed243f08c --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/BlePlxTestApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct BlePlxTestApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/ContentView.swift b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/ContentView.swift new file mode 100644 index 000000000..f65d6753d --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/ContentView.swift @@ -0,0 +1,74 @@ +import SwiftUI + +struct ContentView: View { + @StateObject private var bleManager = BLEPeripheralManager() + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + // Title + Text("BlePlxTest") + .font(.largeTitle) + .bold() + .padding(.horizontal) + .padding(.top, 8) + + Divider().padding(.vertical, 4) + + // Status section + VStack(alignment: .leading, spacing: 4) { + Text("Status") + .font(.headline) + Text(bleManager.statusText) + .font(.subheadline) + .foregroundColor(.secondary) + if !bleManager.peripheralIdentifier.isEmpty { + Text("ID: \(bleManager.peripheralIdentifier)") + .font(.caption) + .foregroundColor(.secondary) + } + } + .padding(.horizontal) + + Divider().padding(.vertical, 4) + + // Connection section + VStack(alignment: .leading, spacing: 4) { + Text("Connection") + .font(.headline) + Text(bleManager.connectionText) + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(.horizontal) + + Divider().padding(.vertical, 4) + + // Log section + VStack(alignment: .leading, spacing: 4) { + Text("Log") + .font(.headline) + .padding(.horizontal) + + ScrollViewReader { proxy in + ScrollView { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(Array(bleManager.logLines.enumerated()), id: \.offset) { index, line in + Text(line) + .font(.system(.caption, design: .monospaced)) + .id(index) + } + } + .padding(.horizontal) + } + .onChange(of: bleManager.logLines.count) { _ in + if let last = bleManager.logLines.indices.last { + withAnimation { + proxy.scrollTo(last, anchor: .bottom) + } + } + } + } + } + } + } +} diff --git a/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Info.plist b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Info.plist new file mode 100644 index 000000000..f3704acdd --- /dev/null +++ b/test-peripheral-app-ios/BlePlxTest/BlePlxTest/Info.plist @@ -0,0 +1,14 @@ + + + + + NSBluetoothAlwaysUsageDescription + BlePlxTest needs Bluetooth to act as a BLE peripheral for testing. + NSBluetoothPeripheralUsageDescription + BlePlxTest needs Bluetooth to advertise as a BLE peripheral. + UIBackgroundModes + + bluetooth-peripheral + + + diff --git a/test-peripheral-app/app/build.gradle.kts b/test-peripheral-app/app/build.gradle.kts new file mode 100644 index 000000000..1f5a66cfd --- /dev/null +++ b/test-peripheral-app/app/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.bleplx.testperipheral" + compileSdk = 34 + + defaultConfig { + applicationId = "com.bleplx.testperipheral" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") +} diff --git a/test-peripheral-app/app/src/main/AndroidManifest.xml b/test-peripheral-app/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ea04f0027 --- /dev/null +++ b/test-peripheral-app/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test-peripheral-app/app/src/main/java/com/bleplx/testperipheral/MainActivity.kt b/test-peripheral-app/app/src/main/java/com/bleplx/testperipheral/MainActivity.kt new file mode 100644 index 000000000..2b487e3e2 --- /dev/null +++ b/test-peripheral-app/app/src/main/java/com/bleplx/testperipheral/MainActivity.kt @@ -0,0 +1,533 @@ +package com.bleplx.testperipheral + +import android.Manifest +import android.annotation.SuppressLint +import android.bluetooth.* +import android.bluetooth.le.AdvertiseCallback +import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertiseSettings +import android.bluetooth.le.BluetoothLeAdvertiser +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.ParcelUuid +import android.util.Log +import android.widget.ScrollView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.UUID + +class MainActivity : AppCompatActivity() { + + companion object { + private val SERVICE_UUID = UUID.fromString("12345678-1234-1234-1234-123456789abc") + private val READ_COUNTER_UUID = UUID.fromString("12345678-1234-1234-1234-123456789a01") + private val WRITE_ECHO_UUID = UUID.fromString("12345678-1234-1234-1234-123456789a02") + private val NOTIFY_STREAM_UUID = UUID.fromString("12345678-1234-1234-1234-123456789a03") + private val INDICATE_STREAM_UUID = UUID.fromString("12345678-1234-1234-1234-123456789a04") + private val MTU_TEST_UUID = UUID.fromString("12345678-1234-1234-1234-123456789a05") + private val WRITE_NO_RESPONSE_UUID = UUID.fromString("12345678-1234-1234-1234-123456789A06") + private val L2CAP_PSM_UUID = UUID.fromString("12345678-1234-1234-1234-123456789A07") + private val CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") + private const val TAG = "BlePlxTest" + + private const val REQUEST_PERMISSIONS = 1 + } + + private lateinit var statusText: TextView + private lateinit var connectionText: TextView + private lateinit var logText: TextView + + private val handler = Handler(Looper.getMainLooper()) + private var bluetoothManager: BluetoothManager? = null + private var gattServer: BluetoothGattServer? = null + private var advertiser: BluetoothLeAdvertiser? = null + + private var connectedDevice: BluetoothDevice? = null + private var negotiatedMtu: Int = 23 + + private var readCounter: Int = 0 + private var echoData: ByteArray = ByteArray(0) + private var noResponseData: ByteArray = ByteArray(0) + + private var l2capServerSocket: BluetoothServerSocket? = null + private var l2capThread: Thread? = null + private var l2capPsm: Int = 0 + + private var notifyCounter: Int = 0 + private var indicateCounter: Int = 0 + private var notifyEnabled = false + private var indicateEnabled = false + private var indicateInFlight = false + + private val notifyRunnable = object : Runnable { + override fun run() { + if (notifyEnabled && connectedDevice != null) { + sendNotification() + handler.postDelayed(this, 500) + } + } + } + + private val indicateRunnable = object : Runnable { + override fun run() { + if (indicateEnabled && connectedDevice != null && !indicateInFlight) { + sendIndication() + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + statusText = findViewById(R.id.statusText) + connectionText = findViewById(R.id.connectionText) + logText = findViewById(R.id.logText) + + bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager + + if (checkPermissions()) { + startPeripheral() + } else { + requestPermissions() + } + } + + override fun onDestroy() { + super.onDestroy() + handler.removeCallbacksAndMessages(null) + try { + l2capServerSocket?.close() + } catch (_: Exception) {} + l2capThread?.interrupt() + try { + gattServer?.close() + advertiser?.stopAdvertising(advertiseCallback) + } catch (_: SecurityException) {} + } + + private fun checkPermissions(): Boolean { + return ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADVERTISE) == PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED + } + + private fun requestPermissions() { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_CONNECT), + REQUEST_PERMISSIONS + ) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_PERMISSIONS && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { + startPeripheral() + } else { + statusText.text = "Permissions denied" + } + } + + private fun startPeripheral() { + try { + openGattServer() + startAdvertising() + } catch (e: SecurityException) { + statusText.text = "SecurityException: ${e.message}" + } + } + + private fun openGattServer() { + gattServer = bluetoothManager?.openGattServer(this, gattCallback) + ?: throw IllegalStateException("Cannot open GATT server") + + val service = BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY) + + // Read Counter + val readCounterChar = BluetoothGattCharacteristic( + READ_COUNTER_UUID, + BluetoothGattCharacteristic.PROPERTY_READ, + BluetoothGattCharacteristic.PERMISSION_READ + ) + service.addCharacteristic(readCounterChar) + + // Write Echo + val writeEchoChar = BluetoothGattCharacteristic( + WRITE_ECHO_UUID, + BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE, + BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE + ) + service.addCharacteristic(writeEchoChar) + + // Notify Stream + val notifyChar = BluetoothGattCharacteristic( + NOTIFY_STREAM_UUID, + BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_NOTIFY, + BluetoothGattCharacteristic.PERMISSION_READ + ) + notifyChar.addDescriptor(BluetoothGattDescriptor(CCCD_UUID, + BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE)) + service.addCharacteristic(notifyChar) + + // Indicate Stream + val indicateChar = BluetoothGattCharacteristic( + INDICATE_STREAM_UUID, + BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_INDICATE, + BluetoothGattCharacteristic.PERMISSION_READ + ) + indicateChar.addDescriptor(BluetoothGattDescriptor(CCCD_UUID, + BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE)) + service.addCharacteristic(indicateChar) + + // MTU Test + val mtuChar = BluetoothGattCharacteristic( + MTU_TEST_UUID, + BluetoothGattCharacteristic.PROPERTY_READ, + BluetoothGattCharacteristic.PERMISSION_READ + ) + service.addCharacteristic(mtuChar) + + // Write Without Response + val writeNoResponseChar = BluetoothGattCharacteristic( + WRITE_NO_RESPONSE_UUID, + BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, + BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE + ) + service.addCharacteristic(writeNoResponseChar) + + // L2CAP PSM + val l2capPsmChar = BluetoothGattCharacteristic( + L2CAP_PSM_UUID, + BluetoothGattCharacteristic.PROPERTY_READ, + BluetoothGattCharacteristic.PERMISSION_READ + ) + service.addCharacteristic(l2capPsmChar) + + gattServer?.addService(service) + log("GATT server opened with test service") + + startL2capServer() + } + + private fun startAdvertising() { + val adapter = bluetoothManager?.adapter ?: return + adapter.name = "BlePlxTest" + advertiser = adapter.bluetoothLeAdvertiser + + if (advertiser == null) { + statusText.text = "BLE advertising not supported" + return + } + + val settings = AdvertiseSettings.Builder() + .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) + .setConnectable(true) + .setTimeout(0) + .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) + .build() + + val data = AdvertiseData.Builder() + .setIncludeDeviceName(true) + .build() + + val scanResponse = AdvertiseData.Builder() + .addServiceUuid(ParcelUuid(SERVICE_UUID)) + .build() + + advertiser?.startAdvertising(settings, data, scanResponse, advertiseCallback) + } + + private val advertiseCallback = object : AdvertiseCallback() { + override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) { + runOnUiThread { + val adapter = BluetoothAdapter.getDefaultAdapter() + val address = adapter?.address ?: "unknown" + statusText.text = "Advertising...\nAdapter name: ${adapter?.name}\nMAC: $address" + log("Advertising started as '${adapter?.name}', address: $address") + } + } + + override fun onStartFailure(errorCode: Int) { + runOnUiThread { + statusText.text = "Advertising failed: $errorCode" + log("Advertising failed with error: $errorCode") + } + } + } + + private val gattCallback = object : BluetoothGattServerCallback() { + + override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) { + runOnUiThread { + if (newState == BluetoothProfile.STATE_CONNECTED) { + connectedDevice = device + negotiatedMtu = 23 + connectionText.text = "Connected: ${device.address}" + log("Device connected: ${device.address}") + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + notifyEnabled = false + indicateEnabled = false + handler.removeCallbacks(notifyRunnable) + handler.removeCallbacks(indicateRunnable) + connectedDevice = null + connectionText.text = "No connections" + log("Device disconnected: ${device.address}") + } + } + } + + override fun onCharacteristicReadRequest( + device: BluetoothDevice, requestId: Int, offset: Int, + characteristic: BluetoothGattCharacteristic + ) { + try { + val value = when (characteristic.uuid) { + READ_COUNTER_UUID -> { + readCounter++ + uint32ToBytes(readCounter) + } + WRITE_ECHO_UUID -> echoData + NOTIFY_STREAM_UUID -> uint32ToBytes(notifyCounter) + INDICATE_STREAM_UUID -> uint32ToBytes(indicateCounter) + MTU_TEST_UUID -> uint16ToBytes(negotiatedMtu) + WRITE_NO_RESPONSE_UUID -> noResponseData + L2CAP_PSM_UUID -> uint16ToBytes(l2capPsm) + else -> ByteArray(0) + } + val responseValue = if (offset < value.size) value.copyOfRange(offset, value.size) else ByteArray(0) + gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, responseValue) + runOnUiThread { log("READ ${shortUuid(characteristic.uuid)} -> ${value.contentToString()}") } + } catch (e: SecurityException) { + runOnUiThread { log("SecurityException on read: ${e.message}") } + } + } + + override fun onCharacteristicWriteRequest( + device: BluetoothDevice, requestId: Int, + characteristic: BluetoothGattCharacteristic, + preparedWrite: Boolean, responseNeeded: Boolean, + offset: Int, value: ByteArray + ) { + try { + when (characteristic.uuid) { + WRITE_ECHO_UUID -> { + echoData = value + runOnUiThread { log("WRITE ${shortUuid(characteristic.uuid)} <- ${value.contentToString()}") } + } + WRITE_NO_RESPONSE_UUID -> { + noResponseData = value + runOnUiThread { log("WRITE_NO_RESPONSE ${shortUuid(characteristic.uuid)} <- ${value.contentToString()}") } + } + } + if (responseNeeded) { + gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) + } + } catch (e: SecurityException) { + runOnUiThread { log("SecurityException on write: ${e.message}") } + } + } + + override fun onDescriptorReadRequest( + device: BluetoothDevice, requestId: Int, offset: Int, + descriptor: BluetoothGattDescriptor + ) { + try { + if (descriptor.uuid == CCCD_UUID) { + val charUuid = descriptor.characteristic.uuid + val value = when (charUuid) { + NOTIFY_STREAM_UUID -> if (notifyEnabled) + BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE + INDICATE_STREAM_UUID -> if (indicateEnabled) + BluetoothGattDescriptor.ENABLE_INDICATION_VALUE + else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE + else -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE + } + gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) + } else { + gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, descriptor.value ?: ByteArray(0)) + } + } catch (e: SecurityException) { + runOnUiThread { log("SecurityException on descriptor read: ${e.message}") } + } + } + + override fun onDescriptorWriteRequest( + device: BluetoothDevice, requestId: Int, + descriptor: BluetoothGattDescriptor, + preparedWrite: Boolean, responseNeeded: Boolean, + offset: Int, value: ByteArray + ) { + try { + if (descriptor.uuid == CCCD_UUID) { + val charUuid = descriptor.characteristic.uuid + when (charUuid) { + NOTIFY_STREAM_UUID -> { + notifyEnabled = value.contentEquals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) + if (notifyEnabled) { + notifyCounter = 0 + handler.post(notifyRunnable) + runOnUiThread { log("NOTIFY enabled") } + } else { + handler.removeCallbacks(notifyRunnable) + runOnUiThread { log("NOTIFY disabled") } + } + } + INDICATE_STREAM_UUID -> { + indicateEnabled = value.contentEquals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) + if (indicateEnabled) { + indicateCounter = 0 + indicateInFlight = false + handler.post(indicateRunnable) + runOnUiThread { log("INDICATE enabled") } + } else { + handler.removeCallbacks(indicateRunnable) + runOnUiThread { log("INDICATE disabled") } + } + } + } + } + if (responseNeeded) { + gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) + } + } catch (e: SecurityException) { + runOnUiThread { log("SecurityException on descriptor write: ${e.message}") } + } + } + + override fun onMtuChanged(device: BluetoothDevice, mtu: Int) { + negotiatedMtu = mtu + runOnUiThread { log("MTU changed: $mtu") } + } + + override fun onNotificationSent(device: BluetoothDevice, status: Int) { + if (indicateEnabled && indicateInFlight) { + indicateInFlight = false + handler.postDelayed(indicateRunnable, 1000) + } + } + } + + private fun sendNotification() { + val device = connectedDevice ?: return + val char = gattServer?.getService(SERVICE_UUID)?.getCharacteristic(NOTIFY_STREAM_UUID) ?: return + notifyCounter++ + try { + char.value = uint32ToBytes(notifyCounter) + gattServer?.notifyCharacteristicChanged(device, char, false) + } catch (e: SecurityException) { + runOnUiThread { log("SecurityException on notify: ${e.message}") } + } + } + + private fun sendIndication() { + val device = connectedDevice ?: return + val char = gattServer?.getService(SERVICE_UUID)?.getCharacteristic(INDICATE_STREAM_UUID) ?: return + indicateCounter++ + indicateInFlight = true + try { + char.value = uint32ToBytes(indicateCounter) + gattServer?.notifyCharacteristicChanged(device, char, true) + } catch (e: SecurityException) { + indicateInFlight = false + runOnUiThread { log("SecurityException on indicate: ${e.message}") } + } + } + + @SuppressLint("MissingPermission") + private fun startL2capServer() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + Log.w(TAG, "L2CAP server requires API 29+, skipping") + runOnUiThread { log("L2CAP server requires API 29+, skipping") } + return + } + + try { + val adapter = bluetoothManager?.adapter ?: return + l2capServerSocket = adapter.listenUsingInsecureL2capChannel() + l2capPsm = l2capServerSocket!!.psm + Log.i(TAG, "L2CAP server listening on PSM: $l2capPsm") + runOnUiThread { log("L2CAP server listening on PSM: $l2capPsm") } + + l2capThread = Thread { + while (!Thread.currentThread().isInterrupted) { + try { + val socket = l2capServerSocket?.accept() ?: break + Log.i(TAG, "L2CAP client connected: ${socket.remoteDevice?.address}") + runOnUiThread { log("L2CAP client connected: ${socket.remoteDevice?.address}") } + + // Handle each connection in its own thread + Thread { + handleL2capConnection(socket) + }.start() + } catch (e: Exception) { + if (!Thread.currentThread().isInterrupted) { + Log.e(TAG, "L2CAP accept error: ${e.message}") + runOnUiThread { log("L2CAP accept error: ${e.message}") } + } + break + } + } + } + l2capThread?.isDaemon = true + l2capThread?.start() + } catch (e: Exception) { + Log.e(TAG, "Failed to start L2CAP server: ${e.message}") + runOnUiThread { log("Failed to start L2CAP server: ${e.message}") } + } + } + + private fun handleL2capConnection(socket: BluetoothSocket) { + try { + val inputStream: InputStream = socket.inputStream + val outputStream: OutputStream = socket.outputStream + val buffer = ByteArray(512) + + while (!Thread.currentThread().isInterrupted) { + val bytesRead = inputStream.read(buffer) + if (bytesRead == -1) break + + Log.i(TAG, "L2CAP read $bytesRead bytes") + runOnUiThread { log("L2CAP read $bytesRead bytes") } + + outputStream.write(buffer, 0, bytesRead) + outputStream.flush() + Log.i(TAG, "L2CAP echoed $bytesRead bytes") + runOnUiThread { log("L2CAP echoed $bytesRead bytes") } + } + } catch (e: Exception) { + Log.e(TAG, "L2CAP connection error: ${e.message}") + runOnUiThread { log("L2CAP connection closed: ${e.message}") } + } finally { + try { socket.close() } catch (_: Exception) {} + } + } + + private fun uint32ToBytes(value: Int): ByteArray { + return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array() + } + + private fun uint16ToBytes(value: Int): ByteArray { + return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value.toShort()).array() + } + + private fun shortUuid(uuid: UUID): String { + return uuid.toString().takeLast(4) + } + + private fun log(message: String) { + val current = logText.text.toString() + val lines = current.split("\n").takeLast(50) + logText.text = (lines + message).joinToString("\n") + val scrollView = logText.parent as? ScrollView + scrollView?.post { scrollView.fullScroll(ScrollView.FOCUS_DOWN) } + } +} diff --git a/test-peripheral-app/app/src/main/res/layout/activity_main.xml b/test-peripheral-app/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..ab116d131 --- /dev/null +++ b/test-peripheral-app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/test-peripheral-app/build.gradle.kts b/test-peripheral-app/build.gradle.kts new file mode 100644 index 000000000..e3e570a3b --- /dev/null +++ b/test-peripheral-app/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "1.9.22" apply false +} diff --git a/test-peripheral-app/gradle.properties b/test-peripheral-app/gradle.properties new file mode 100644 index 000000000..5bac8ac50 --- /dev/null +++ b/test-peripheral-app/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/test-peripheral-app/gradle/wrapper/gradle-wrapper.jar b/test-peripheral-app/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..8bdaf60c7 Binary files /dev/null and b/test-peripheral-app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example-expo/android/gradle/wrapper/gradle-wrapper.properties b/test-peripheral-app/gradle/wrapper/gradle-wrapper.properties similarity index 74% rename from example-expo/android/gradle/wrapper/gradle-wrapper.properties rename to test-peripheral-app/gradle/wrapper/gradle-wrapper.properties index 2ea3535dc..2733ed5dc 100644 --- a/example-expo/android/gradle/wrapper/gradle-wrapper.properties +++ b/test-peripheral-app/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip -networkTimeout=10000 -validateDistributionUrl=true +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/example-expo/android/gradlew b/test-peripheral-app/gradlew similarity index 94% rename from example-expo/android/gradlew rename to test-peripheral-app/gradlew index 1aa94a426..ef07e0162 100755 --- a/example-expo/android/gradlew +++ b/test-peripheral-app/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/test-peripheral-app/settings.gradle.kts b/test-peripheral-app/settings.gradle.kts new file mode 100644 index 000000000..efb0db397 --- /dev/null +++ b/test-peripheral-app/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "TestPeripheral" +include(":app") diff --git a/tsconfig.build.json b/tsconfig.build.json index 2a21c289e..f3351022e 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,11 @@ { "extends": "./tsconfig", - "exclude": ["example"] + "exclude": [ + "example", + "example-expo", + "__tests__", + "integration-tests", + "src/specs", + "src/index.d.ts" + ] } diff --git a/tsconfig.json b/tsconfig.json index 02de3e9c0..846f30023 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "target": "esnext", "verbatimModuleSyntax": true }, - "exclude": ["src/**", "__tests__/**", "integration-tests/**"] + "exclude": ["integration-tests/**", "example/**", "example-expo/**", "src/specs/**", "src/index.d.ts", "__tests__/**"] } diff --git a/tsconfig.specs.json b/tsconfig.specs.json new file mode 100644 index 000000000..2bfd6f347 --- /dev/null +++ b/tsconfig.specs.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "rootDir": "./", + "esModuleInterop": true, + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": true, + "strict": true, + "target": "esnext", + "noEmit": true + }, + "include": ["src/specs"] +} diff --git a/tsconfig.tests.json b/tsconfig.tests.json new file mode 100644 index 000000000..08bef073f --- /dev/null +++ b/tsconfig.tests.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noUnusedLocals": false, + "noUnusedParameters": false, + "verbatimModuleSyntax": false, + "noUncheckedIndexedAccess": false + }, + "exclude": ["integration-tests/**", "example/**", "example-expo/**", "src/specs/**", "src/index.d.ts"], + "include": ["src/**/*", "__tests__/**/*"] +} diff --git a/yarn.lock b/yarn.lock index 6f6bc6bb3..f4fc3ef36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52,7 +52,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.18.5", "@babel/core@^7.20.0", "@babel/core@^7.23.9", "@babel/core@^7.9.0": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.18.5", "@babel/core@^7.20.0", "@babel/core@^7.23.9": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== @@ -125,7 +125,7 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" -"@babel/generator@^7.26.0", "@babel/generator@^7.26.3", "@babel/generator@^7.7.2", "@babel/generator@^7.9.4": +"@babel/generator@^7.26.0", "@babel/generator@^7.26.3", "@babel/generator@^7.7.2": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== @@ -237,7 +237,7 @@ dependencies: "@babel/types" "^7.25.9" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== @@ -323,11 +323,6 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@7.9.4": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== - "@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" @@ -391,7 +386,7 @@ "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.13.0", "@babel/plugin-proposal-class-properties@^7.17.12", "@babel/plugin-proposal-class-properties@^7.18.0", "@babel/plugin-proposal-class-properties@^7.8.3": +"@babel/plugin-proposal-class-properties@^7.13.0", "@babel/plugin-proposal-class-properties@^7.17.12", "@babel/plugin-proposal-class-properties@^7.18.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== @@ -399,7 +394,7 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-decorators@^7.12.9", "@babel/plugin-proposal-decorators@^7.8.3": +"@babel/plugin-proposal-decorators@^7.12.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz#8680707f943d1a3da2cd66b948179920f097e254" integrity sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g== @@ -408,52 +403,14 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-syntax-decorators" "^7.25.9" -"@babel/plugin-proposal-do-expressions@^7.8.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-do-expressions/-/plugin-proposal-do-expressions-7.25.9.tgz#c1eae77aeb86fcd689804378acefd9e8f1ca8a27" - integrity sha512-0IkO77tw2OcZua/ADovH//IEiUyQpNjWvLyMFNidXnZx4eEriQjwkH9t/EyQZUaQu0KOxxdszC7m8VUVs51ydg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-proposal-export-default-from@^7.0.0", "@babel/plugin-proposal-export-default-from@^7.24.7", "@babel/plugin-proposal-export-default-from@^7.8.3": +"@babel/plugin-proposal-export-default-from@^7.0.0", "@babel/plugin-proposal-export-default-from@^7.24.7": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz#52702be6ef8367fc8f18b8438278332beeb8f87c" integrity sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ== dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-proposal-export-namespace-from@^7.8.3": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-function-bind@^7.8.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-bind/-/plugin-proposal-function-bind-7.25.9.tgz#ef98187f3d2cb87abf0f815f43bf4f26c1a6d968" - integrity sha512-1g0b0XU667A2IZNdhovGr0ZdywJxf081B8JN5qyiNqzJK7GtdYBxGcuA+lq7q8OgO4cAc4vF57Ad0XLoDBsJAg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-proposal-function-sent@^7.8.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.25.9.tgz#50eccfe5a6a518f9f264eacfc9a9865dcb2bfa85" - integrity sha512-Qi9KEBTY6WAjHBeHJ1jm4HyGlwvZLfjUaxO9g1jKHqyQPe6c+q7DlKgyrBUH7v+VWLJ0bNy5cQlXHtOV5/uibw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-wrap-function" "^7.25.9" - -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" - integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.18.0", "@babel/plugin-proposal-logical-assignment-operators@^7.8.3": +"@babel/plugin-proposal-logical-assignment-operators@^7.18.0": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== @@ -461,7 +418,7 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== @@ -469,7 +426,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.0.0", "@babel/plugin-proposal-numeric-separator@^7.8.3": +"@babel/plugin-proposal-numeric-separator@^7.0.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== @@ -496,7 +453,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.20.0", "@babel/plugin-proposal-optional-chaining@^7.9.0": +"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.20.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -505,34 +462,11 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-pipeline-operator@^7.8.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-pipeline-operator/-/plugin-proposal-pipeline-operator-7.25.9.tgz#8494cd391e0f08de9cb2b19e48978554b4683daa" - integrity sha512-rmb8zOYFdVz6y/OqJn6RfbIBiJPQdUbHg7R5ibym5KM0e8uNGdU9yfn9cjkBLwS22Lqd+ey3D8/UvK5GLyyh5A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-syntax-pipeline-operator" "^7.25.9" - -"@babel/plugin-proposal-private-methods@^7.8.3": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-proposal-throw-expressions@^7.8.3": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.25.9.tgz#9bfba4a4b775dbadfc5ca91a9f22097754142b56" - integrity sha512-Zw62DP6cdbXXEtTNMWYY10rIOPGAWPk8qdqM+AT3JbHtFq8ook0JXJCWdQJTlSVACHo0R6lvoNKO9B1ZVkjClg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -582,13 +516,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.25.9", "@babel/plugin-syntax-flow@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz#96507595c21b45fccfc2bc758d5c45452e6164fa" @@ -610,7 +537,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -673,13 +600,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-pipeline-operator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-pipeline-operator/-/plugin-syntax-pipeline-operator-7.25.9.tgz#5488436b3e0a313716b4ff05d3a4f47970639dd4" - integrity sha512-W0KjBvv8uT4A8DUoRNpXEHkKekqO/PC57doaWCqbJeG0lGxKFh7w7/PHYPmwgF+jKxekNnc+YOMQNCo94d8MJg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" @@ -1200,7 +1120,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.25.9" "@babel/helper-plugin-utils" "^7.25.9" -"@babel/preset-env@^7.18.2", "@babel/preset-env@^7.23.8", "@babel/preset-env@^7.9.0": +"@babel/preset-env@^7.18.2", "@babel/preset-env@^7.23.8": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.0.tgz#30e5c6bc1bcc54865bff0c5a30f6d4ccdc7fa8b1" integrity sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw== @@ -1275,7 +1195,7 @@ core-js-compat "^3.38.1" semver "^6.3.1" -"@babel/preset-flow@^7.13.13", "@babel/preset-flow@^7.17.12", "@babel/preset-flow@^7.24.7", "@babel/preset-flow@^7.25.9", "@babel/preset-flow@^7.9.0": +"@babel/preset-flow@^7.13.13", "@babel/preset-flow@^7.17.12", "@babel/preset-flow@^7.24.7": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.25.9.tgz#ef8b5e7e3f24a42b3711e77fb14919b87dffed0a" integrity sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ== @@ -1293,7 +1213,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.17.12", "@babel/preset-react@^7.22.15", "@babel/preset-react@^7.9.4": +"@babel/preset-react@^7.17.12", "@babel/preset-react@^7.22.15": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa" integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw== @@ -1305,11 +1225,6 @@ "@babel/plugin-transform-react-jsx-development" "^7.25.9" "@babel/plugin-transform-react-pure-annotations" "^7.25.9" -"@babel/preset-stage-0@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/preset-stage-0/-/preset-stage-0-7.8.3.tgz#b6a0eca1a3b72e07f9caf58f998e97568028f6f5" - integrity sha512-+l6FlG1j73t4wh78W41StbcCz0/9a1/y+vxfnjtHl060kSmcgMfGzK9MEkLvrCOXfhp9RCX+d88sm6rOqxEIEQ== - "@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.17.12", "@babel/preset-typescript@^7.23.0", "@babel/preset-typescript@^7.23.3", "@babel/preset-typescript@^7.24.7": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" @@ -1381,7 +1296,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.25.9", "@babel/traverse@^7.9.0": +"@babel/traverse@^7.25.9": version "7.26.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== @@ -1394,7 +1309,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.9.0": +"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -2710,13 +2625,6 @@ resolved "https://registry.yarnpkg.com/@types/react-native-base64/-/react-native-base64-0.2.2.tgz#d4e1d537e6d547d23d96a1e64627acc13587ae6b" integrity sha512-obr+/L9Jaxdr+xCVS/IQcYgreg5xtnui4Wqw/G1acBUtW2CnqVJj6lK6F/5F3+5d2oZEo5xDDLqy8GVn2HbEmw== -"@types/react-native@0.70.0": - version "0.70.0" - resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.70.0.tgz#f8cdcdd542d36467d7591585b93d27e0563676e0" - integrity sha512-yBN7qJDfs0Vwr34NyfW1SWzalHQoYtpUWf0t4UJY9C5ft58BRr46+r92I0v+l3QX4VNsSRMHVAAWqLLCbIkM+g== - dependencies: - "@types/react" "*" - "@types/react-test-renderer@>=16.9.0": version "19.0.0" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-19.0.0.tgz#4cdeace7561bf359ee167f51704f420c07d4bd8d" @@ -2752,11 +2660,6 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== -"@types/unist@^2.0.0", "@types/unist@^2.0.2": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" - integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== - "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -2961,7 +2864,7 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== -JSONStream@^1.0.3, JSONStream@^1.0.4, JSONStream@^1.3.5: +JSONStream@^1.0.4, JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== @@ -3009,11 +2912,6 @@ acorn-walk@^8.0.2, acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -acorn@^5.2.1: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - acorn@^8.1.0, acorn@^8.11.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.14.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" @@ -3096,21 +2994,6 @@ ansi-escapes@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f" integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig== -ansi-html@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha512-JoAxEa1DfP9m2xfB/y2r/aKcwXNlltr4+0QSBC4TrLfcxyvepX2Pv0t/xpgGV5bGsDzCYV8SzjWgyCW0T9yYbA== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== - -ansi-regex@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" - integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== - ansi-regex@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" @@ -3155,14 +3038,6 @@ any-promise@^1.0.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -3171,13 +3046,6 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -append-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" - integrity sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA== - dependencies: - buffer-equal "^1.0.0" - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -3200,21 +3068,6 @@ aria-query@^5.3.2: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -3245,11 +3098,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== - array.prototype.findlast@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" @@ -3328,11 +3176,6 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - ast-types-flow@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" @@ -3359,11 +3202,6 @@ ast-types@^0.16.1: dependencies: tslib "^2.0.1" -async-each@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.6.tgz#52f1d9403818c179b7561e11a5d1b77eb2160e77" - integrity sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg== - async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" @@ -3381,11 +3219,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - atomically@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/atomically/-/atomically-2.0.3.tgz#27e47bbe39994d324918491ba7c0edb7783e56cb" @@ -3506,13 +3339,6 @@ babel-plugin-syntax-hermes-parser@0.25.1: dependencies: hermes-parser "0.25.1" -babel-plugin-syntax-hermes-parser@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.15.1.tgz#d115ee9761a808af590a9b2a0b568115e25ea743" - integrity sha512-ohjBzUCCAJHB4uuE8IgVjmzC4u8xsFm3os52ctrWxX4HaDRP5IuyF+li1fkWmKkMyFQn3260WHZQtvSLcwe6ng== - dependencies: - hermes-parser "0.15.1" - babel-plugin-transform-flow-enums@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz#d1d0cc9bdc799c850ca110d0ddc9f21b9ec3ef25" @@ -3565,16 +3391,6 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" -babelify@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5" - integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg== - -bail@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" - integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -3585,19 +3401,6 @@ base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - basic-ftp@^5.0.2: version "5.0.5" resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0" @@ -3613,23 +3416,11 @@ big-integer@1.6.x: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -3639,16 +3430,6 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -body@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" - integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== - dependencies: - continuable-cache "^0.3.1" - error "^7.0.0" - raw-body "~1.1.0" - safe-json-parse "~1.0.1" - boxen@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-8.0.1.tgz#7e9fcbb45e11a2d7e6daa8fdcebfc3242fc19fe3" @@ -3692,22 +3473,6 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -3715,13 +3480,6 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browser-resolve@^1.7.0: - version "1.11.3" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" - integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== - dependencies: - resolve "1.1.7" - browserslist@^4.20.4, browserslist@^4.24.0, browserslist@^4.24.3: version "4.24.4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" @@ -3746,21 +3504,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -buffer-equal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.1.tgz#2f7651be5b1b3f057fcd6e7ee16cf34767077d90" - integrity sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg== - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-shims@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" - integrity sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g== - buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -3776,31 +3524,6 @@ bundle-name@^4.1.0: dependencies: run-applescript "^7.0.0" -bytes@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" - integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cached-path-relative@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.1.0.tgz#865576dfef39c0d6a7defde794d078f5308e3ef3" - integrity sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA== - call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" @@ -3870,7 +3593,7 @@ camelcase-keys@^7.0.0: quick-lru "^5.1.1" type-fest "^1.2.1" -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -3895,11 +3618,6 @@ caniuse-lite@^1.0.30001688: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== -ccount@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" - integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== - chalk@4, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -3913,7 +3631,7 @@ chalk@5.4.1, chalk@^5.3.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== -chalk@^2.3.0, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -3940,50 +3658,11 @@ char-regex@^2.0.0: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.2.tgz#81385bb071af4df774bff8721d0ca15ef29ea0bb" integrity sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg== -character-entities-html4@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" - integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== - -character-entities-legacy@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" - integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== - -character-entities@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" - integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== - -character-reference-invalid@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" - integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^2.0.4: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -4041,16 +3720,6 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -4092,15 +3761,6 @@ cli-width@^4.1.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -4119,11 +3779,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - integrity sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g== - clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -4133,58 +3788,21 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - integrity sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag== - clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clone@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - -cloneable-readable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" - integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== - dependencies: - inherits "^2.0.1" - process-nextick-args "^2.0.0" - readable-stream "^2.3.5" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== - -collapse-white-space@^1.0.0, collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== - collect-v8-coverage@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -4216,11 +3834,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -comma-separated-tokens@^1.0.1: - version "1.0.8" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" - integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== - commander@^12.0.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -4262,26 +3875,11 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -component-emitter@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" - integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - concat-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" @@ -4292,15 +3890,6 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -concat-stream@~1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" - integrity sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ== - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" - config-chain@^1.1.11: version "1.1.13" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" @@ -4334,11 +3923,6 @@ connect@^3.6.5: parseurl "~1.3.3" utils-merge "1.0.1" -continuable-cache@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" - integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== - conventional-changelog-angular@^5.0.12: version "5.0.13" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" @@ -4521,21 +4105,11 @@ conventional-recommended-bump@^6.1.0: meow "^8.0.0" q "^1.5.1" -convert-source-map@^1.5.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - core-js-compat@^3.38.0, core-js-compat@^3.38.1: version "3.40.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38" @@ -4612,17 +4186,6 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^6.0.0: - version "6.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57" - integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -4724,12 +4287,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -de-indent@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" - integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -4743,7 +4301,7 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "^2.1.3" -debug@^3.1.0, debug@^3.2.7: +debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4758,7 +4316,7 @@ decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.2.0: +decamelize@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -4773,7 +4331,7 @@ decimal.js@^10.4.2: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decode-uri-component@^0.2.0, decode-uri-component@^0.2.2: +decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== @@ -4846,33 +4404,6 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -defined@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" - integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== - degenerator@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" @@ -4938,26 +4469,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detab@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" - integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== - dependencies: - repeat-string "^1.5.4" - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -detective@^4.0.0: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -4975,13 +4491,6 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -doctrine-temporary-fork@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz#36f2154f556ee4f1e60311d391cd23de5187ed57" - integrity sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA== - dependencies: - esutils "^2.0.2" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -4996,78 +4505,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -documentation@12.3.0: - version "12.3.0" - resolved "https://registry.yarnpkg.com/documentation/-/documentation-12.3.0.tgz#2bf429433a1edcb32dd35f60bcdc95bf3858ae4a" - integrity sha512-qjEcTyC5jjGUOedRvumC/gCyon2WynfWtcjxDAna23CnRnYwD6Q6ATCRGZk+2wyf6GBpr7o5F77fgtHrjfuIxQ== - dependencies: - "@babel/core" "^7.9.0" - "@babel/generator" "^7.9.4" - "@babel/parser" "7.9.4" - "@babel/plugin-proposal-class-properties" "^7.8.3" - "@babel/plugin-proposal-decorators" "^7.8.3" - "@babel/plugin-proposal-do-expressions" "^7.8.3" - "@babel/plugin-proposal-export-default-from" "^7.8.3" - "@babel/plugin-proposal-export-namespace-from" "^7.8.3" - "@babel/plugin-proposal-function-bind" "^7.8.3" - "@babel/plugin-proposal-function-sent" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-logical-assignment-operators" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-pipeline-operator" "^7.8.3" - "@babel/plugin-proposal-private-methods" "^7.8.3" - "@babel/plugin-proposal-throw-expressions" "^7.8.3" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/preset-env" "^7.9.0" - "@babel/preset-flow" "^7.9.0" - "@babel/preset-react" "^7.9.4" - "@babel/preset-stage-0" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - ansi-html "^0.0.7" - babelify "^10.0.0" - chalk "^2.3.0" - chokidar "^2.0.4" - concat-stream "^1.6.0" - diff "^4.0.1" - doctrine-temporary-fork "2.1.0" - get-port "^4.0.0" - git-url-parse "^11.1.2" - github-slugger "1.2.0" - glob "^7.1.2" - globals-docs "^2.4.0" - highlight.js "^9.15.5" - ini "^1.3.5" - js-yaml "^3.10.0" - lodash "^4.17.10" - mdast-util-inject "^1.1.0" - micromatch "^3.1.5" - mime "^2.2.0" - module-deps-sortable "5.0.0" - parse-filepath "^1.0.2" - pify "^4.0.0" - read-pkg-up "^4.0.0" - remark "^9.0.0" - remark-html "^8.0.0" - remark-reference-links "^4.0.1" - remark-toc "^5.0.0" - resolve "^1.8.1" - stream-array "^1.1.2" - strip-json-comments "^2.0.1" - tiny-lr "^1.1.0" - unist-builder "^1.0.2" - unist-util-visit "^1.3.0" - vfile "^4.0.0" - vfile-reporter "^6.0.0" - vfile-sort "^2.1.0" - vinyl "^2.1.0" - vinyl-fs "^3.0.2" - vue-template-compiler "^2.5.16" - yargs "^12.0.2" - domexception@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" @@ -5098,23 +4535,6 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" -duplexer2@^0.1.2, duplexer2@~0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== - dependencies: - readable-stream "^2.0.2" - -duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -5130,11 +4550,6 @@ emittery@^0.13.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== -"emoji-regex@>=6.0.0 <=6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" - integrity sha512-WfVwM9e+M9B/4Qjh9SRnPX2A74Tom3WlVfWF9QWJ8f2BPa1u+/q4aEp1tizZ3vBKAZTg7B6yxn3t9iMjT+dv4w== - emoji-regex@^10.3.0: version "10.4.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" @@ -5160,7 +4575,7 @@ encodeurl@~2.0.0: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -5191,13 +4606,6 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -error@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" - integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== - dependencies: - string-template "~0.2.1" - es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9: version "1.23.9" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606" @@ -5448,14 +4856,6 @@ eslint-plugin-eslint-comments@^3.2.0: escape-string-regexp "^1.0.5" ignore "^5.0.5" -eslint-plugin-flowtype@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912" - integrity sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ== - dependencies: - lodash "^4.17.21" - string-natural-compare "^3.0.1" - eslint-plugin-ft-flow@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz#3b3c113c41902bcbacf0e22b536debcfc3c819e8" @@ -5464,6 +4864,14 @@ eslint-plugin-ft-flow@^2.0.1: lodash "^4.17.21" string-natural-compare "^3.0.1" +eslint-plugin-ft-flow@^3.0.11: + version "3.0.11" + resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-3.0.11.tgz#64654dad277104fc45aef8d009c4a51057ea1250" + integrity sha512-6ZJ4KYGYjIosCcU883zBBT1nFsKP58xrTOwguiw3/HRq0EpYAyhrF1nCGbK7V23cmKtPXMpDfl8qPupt5s5W8w== + dependencies: + lodash "^4.17.21" + string-natural-compare "^3.0.1" + eslint-plugin-import@^2.27.5, eslint-plugin-import@^2.28.1: version "2.31.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7" @@ -5740,19 +5148,6 @@ execa@8.0.0: signal-exit "^4.1.0" strip-final-newline "^3.0.0" -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -5788,19 +5183,6 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" @@ -5842,26 +5224,6 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - external-editor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -5871,20 +5233,6 @@ external-editor@^3.1.0: iconv-lite "^0.4.24" tmp "^0.0.33" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -5928,13 +5276,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== - dependencies: - websocket-driver ">=0.5.1" - fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -5949,21 +5290,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -6039,20 +5365,6 @@ flat-cache@^3.0.4: version "3.2.0" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - -flatted@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" - integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== - -flow-bin@^0.259.1: - version "0.259.1" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.259.1.tgz#9ff26e014dc53beca773f7387fe58c66fe7fd791" - integrity sha512-c5vsjZ19tWGO1ldc3gNmrH6eDdB0W428xPluIDB8UNm1eMCsBM/otZUoE3dmZuWUg/+5/1eKc28Z57/JUVVslQ== flow-enums-runtime@^0.0.6: version "0.0.6" @@ -6064,14 +5376,6 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.258.1.tgz#e90ef922e5fc1ff1cff26f6e547e4d4197770859" integrity sha512-Y8CrO98EcXVCiYE4s5z0LTMbeYjKyd3MAEUJqxA7B8yGRlmdrG5UDqq4pVrUAfAu2tMFgpQESvBhBu9Xg1tpow== -flush-write-stream@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -6079,11 +5383,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - form-data@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" @@ -6093,13 +5392,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -6123,14 +5415,6 @@ fs-extra@^11.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-mkdirp-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" - integrity sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ== - dependencies: - graceful-fs "^4.1.11" - through2 "^2.0.3" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -6141,14 +5425,6 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -6181,11 +5457,6 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -6227,11 +5498,6 @@ get-pkg-repo@^4.0.0: through2 "^2.0.0" yargs "^16.2.0" -get-port@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" - integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== - get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" @@ -6240,13 +5506,6 @@ get-proto@^1.0.0, get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - get-stream@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -6282,11 +5541,6 @@ get-uri@^6.0.1: data-uri-to-buffer "^6.0.2" debug "^4.3.4" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - getenv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" @@ -6319,14 +5573,6 @@ git-semver-tags@^4.1.1: meow "^8.0.0" semver "^6.0.0" -git-up@^4.0.0: - version "4.0.5" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" - integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== - dependencies: - is-ssh "^1.3.0" - parse-url "^6.0.0" - git-up@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" @@ -6342,13 +5588,6 @@ git-url-parse@14.0.0: dependencies: git-up "^7.0.0" -git-url-parse@^11.1.2: - version "11.6.0" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" - integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g== - dependencies: - git-up "^4.0.0" - gitconfiglocal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" @@ -6356,26 +5595,6 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -github-slugger@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.0.tgz#8ada3286fd046d8951c3c952a8d7854cfd90fd9a" - integrity sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q== - dependencies: - emoji-regex ">=6.0.0 <=6.1.1" - -github-slugger@^1.0.0, github-slugger@^1.2.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" - integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -6390,22 +5609,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-stream@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" - integrity sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw== - dependencies: - extend "^3.0.0" - glob "^7.1.1" - glob-parent "^3.1.0" - is-negated-glob "^1.0.0" - ordered-read-streams "^1.0.0" - pumpify "^1.3.5" - readable-stream "^2.1.5" - remove-trailing-separator "^1.0.1" - to-absolute-glob "^2.0.0" - unique-stream "^2.0.2" - glob@7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -6418,7 +5621,7 @@ glob@7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7, glob@^7.2.0: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -6455,11 +5658,6 @@ global-dirs@^0.1.1: dependencies: ini "^1.3.4" -globals-docs@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/globals-docs/-/globals-docs-2.4.1.tgz#d16887709f4a15eb22d97e96343591f87a2ee3db" - integrity sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg== - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -6525,7 +5723,7 @@ graceful-fs@4.2.10: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6593,37 +5791,6 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -6631,58 +5798,6 @@ hasown@^2.0.0, hasown@^2.0.2: dependencies: function-bind "^1.1.2" -hast-util-is-element@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425" - integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ== - -hast-util-sanitize@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-1.3.1.tgz#4e60d66336bd67e52354d581967467029a933f2e" - integrity sha512-AIeKHuHx0Wk45nSkGVa2/ujQYTksnDl8gmmKo/mwQi7ag7IBZ8cM3nJ2G86SajbjGP/HRpud6kMkPtcM2i0Tlw== - dependencies: - xtend "^4.0.1" - -hast-util-to-html@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-4.0.1.tgz#3666b05afb62bd69f8f5e6c94db04dea19438e2a" - integrity sha512-2emzwyf0xEsc4TBIPmDJmBttIw8R4SXAJiJZoiRR/s47ODYWgOqNoDbf2SJAbMbfNdFWMiCSOrI3OVnX6Qq2Mg== - dependencies: - ccount "^1.0.0" - comma-separated-tokens "^1.0.1" - hast-util-is-element "^1.0.0" - hast-util-whitespace "^1.0.0" - html-void-elements "^1.0.0" - property-information "^4.0.0" - space-separated-tokens "^1.0.0" - stringify-entities "^1.0.1" - unist-util-is "^2.0.0" - xtend "^4.0.1" - -hast-util-whitespace@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" - integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hermes-eslint@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.15.1.tgz#c5919a6fdbd151febc3d5ed8ff17e5433913528c" - integrity sha512-ArfT3oASsYOkCa29GOw34GR+kpHXqrhhYgXDadK3daJBejrMPbGlUbBTzTlGBuz1CGLxxdC5lwwv3OegPJzfDA== - dependencies: - esrecurse "^4.3.0" - hermes-estree "0.15.1" - hermes-parser "0.15.1" - -hermes-estree@0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.15.1.tgz#d06d4ddf87e91857b0130a083a9d7696d7aec61f" - integrity sha512-XrQH+GATG/8DYbzlrVs6Vf/EDxLhYEHXvzt/Xve4b/NXXpsNLDN8bdBEKp5z0XeOMoL1XMEexxIIf1a5bH6kYA== - hermes-estree@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.19.1.tgz#d5924f5fac2bf0532547ae9f506d6db8f3c96392" @@ -6693,13 +5808,6 @@ hermes-estree@0.25.1: resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== -hermes-parser@0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.15.1.tgz#f02587be85228b22841d50f6839ae42a308e5100" - integrity sha512-38Re105dr4UZ0/EPRApWxtIOWWynQpdOYWO+7nFO8ADd2mXdaHKMCFAxIjkqACa1GLrAtrXqqaJdUYHi/QUbkA== - dependencies: - hermes-estree "0.15.1" - hermes-parser@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.19.1.tgz#1044348097165b7c93dc198a80b04ed5130d6b1a" @@ -6714,11 +5822,6 @@ hermes-parser@0.25.1: dependencies: hermes-estree "0.25.1" -highlight.js@^9.15.5: - version "9.18.5" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" - integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== - hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -6743,11 +5846,6 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-void-elements@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" - integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== - http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -6759,11 +5857,6 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -6890,7 +5983,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6900,7 +5993,7 @@ ini@4.1.1: resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== -ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -6944,11 +6037,6 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - ip-address@^9.0.5: version "9.0.5" resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" @@ -6965,31 +6053,6 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" -is-accessor-descriptor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz#3223b10628354644b86260db29b3e693f5ceedd4" - integrity sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA== - dependencies: - hasown "^2.0.0" - -is-alphabetical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" - integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== - -is-alphanumeric@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" - integrity sha512-ZmRL7++ZkcMOfDuWZuMJyIVLr2keE1o/DeNWh1EmgqGhUcV+9BIVsx0BcSBOHTZqzjs4+dISzr2KAeBEWGgXeA== - -is-alphanumerical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" - integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== - dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -7021,13 +6084,6 @@ is-bigint@^1.1.0: dependencies: has-bigints "^1.0.2" -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== - dependencies: - binary-extensions "^1.0.0" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -7043,16 +6099,6 @@ is-boolean-object@^1.2.1: call-bound "^1.0.2" has-tostringtag "^1.0.2" -is-buffer@^1.1.4, is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== - is-callable@^1.1.3, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -7065,13 +6111,6 @@ is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.16.0, is-core- dependencies: hasown "^2.0.2" -is-data-descriptor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz#2109164426166d32ea38c405c1e0945d9e6a4eeb" - integrity sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw== - dependencies: - hasown "^2.0.0" - is-data-view@^1.0.1, is-data-view@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" @@ -7089,27 +6128,6 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" -is-decimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" - integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== - -is-descriptor@^0.1.0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.7.tgz#2727eb61fd789dcd5bdf0ed4569f551d2fe3be33" - integrity sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg== - dependencies: - is-accessor-descriptor "^1.0.1" - is-data-descriptor "^1.0.1" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.3.tgz#92d27cb3cd311c4977a4db47df457234a13cb306" - integrity sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw== - dependencies: - is-accessor-descriptor "^1.0.1" - is-data-descriptor "^1.0.1" - is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" @@ -7125,19 +6143,7 @@ is-docker@^3.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== @@ -7149,18 +6155,6 @@ is-finalizationregistry@^1.1.0: dependencies: call-bound "^1.0.3" -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -7197,13 +6191,6 @@ is-git-repository@^2.0.0: execa "^4.0.3" is-absolute "^1.0.0" -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== - dependencies: - is-extglob "^2.1.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -7211,11 +6198,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-hexadecimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" - integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== - is-in-ci@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-in-ci/-/is-in-ci-1.0.0.tgz#9a86bbda7e42c6129902e0574c54b018fbb6ab88" @@ -7251,11 +6233,6 @@ is-map@^2.0.3: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== -is-negated-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" - integrity sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug== - is-npm@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261" @@ -7269,13 +6246,6 @@ is-number-object@^1.1.1: call-bound "^1.0.3" has-tostringtag "^1.0.2" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -7311,7 +6281,7 @@ is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -7352,18 +6322,13 @@ is-shared-array-buffer@^1.0.4: dependencies: call-bound "^1.0.3" -is-ssh@^1.3.0, is-ssh@^1.4.0: +is-ssh@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== dependencies: protocols "^2.0.1" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -7427,16 +6392,6 @@ is-unicode-supported@^2.0.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== -is-utf8@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== - -is-valid-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" - integrity sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA== - is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" @@ -7457,21 +6412,11 @@ is-weakset@^2.0.3: call-bound "^1.0.3" get-intrinsic "^1.2.6" -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== - -is-windows@^1.0.1, is-windows@^1.0.2: +is-windows@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -7486,29 +6431,22 @@ is-wsl@^3.1.0: dependencies: is-inside-container "^1.0.0" -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: +isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== @@ -8016,7 +6954,7 @@ jetifier@^2.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.10.0, js-yaml@^3.13.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -8142,11 +7080,6 @@ jsesc@~3.0.2: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -8213,27 +7146,6 @@ jsonparse@^1.2.0: object.assign "^4.1.4" object.values "^1.1.6" -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -8273,27 +7185,6 @@ latest-version@^9.0.0: dependencies: package-json "^10.0.0" -lazystream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" - integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== - dependencies: - readable-stream "^2.0.5" - -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - -lead@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" - integrity sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow== - dependencies: - flush-write-stream "^1.0.2" - leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -8320,11 +7211,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -livereload-js@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" - integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== - load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -8476,11 +7362,6 @@ log-symbols@^6.0.0: chalk "^5.3.0" is-unicode-supported "^1.3.0" -longest-streak@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" - integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== - loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -8539,18 +7420,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - -map-cache@^0.2.0, map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -8561,23 +7430,6 @@ map-obj@^4.0.0, map-obj@^4.1.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - -markdown-table@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" - integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== - marky@^1.2.2: version "1.2.5" resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" @@ -8588,73 +7440,6 @@ math-intrinsics@^1.1.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -mdast-util-compact@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz#d531bb7667b5123abf20859be086c4d06c894593" - integrity sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg== - dependencies: - unist-util-visit "^1.1.0" - -mdast-util-definitions@^1.2.0: - version "1.2.5" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.5.tgz#3fe622a4171c774ebd06f11e9f8af7ec53ea5c74" - integrity sha512-CJXEdoLfiISCDc2JB6QLb79pYfI6+GcIH+W2ox9nMc7od0Pz+bovcHsiq29xAQY6ayqe/9CsK2VzkSJdg1pFYA== - dependencies: - unist-util-visit "^1.0.0" - -mdast-util-inject@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz#db06b8b585be959a2dcd2f87f472ba9b756f3675" - integrity sha512-CcJ0mHa36QYumDKiZ2OIR+ClhfOM7zIzN+Wfy8tRZ1hpH9DKLCS+Mh4DyK5bCxzE9uxMWcbIpeNFWsg1zrj/2g== - dependencies: - mdast-util-to-string "^1.0.0" - -mdast-util-to-hast@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-3.0.4.tgz#132001b266031192348d3366a6b011f28e54dc40" - integrity sha512-/eIbly2YmyVgpJNo+bFLLMCI1XgolO/Ffowhf+pHDq3X4/V6FntC9sGQCDLM147eTS+uSXv5dRzJyFn+o0tazA== - dependencies: - collapse-white-space "^1.0.0" - detab "^2.0.0" - mdast-util-definitions "^1.2.0" - mdurl "^1.0.1" - trim "0.0.1" - trim-lines "^1.0.0" - unist-builder "^1.0.1" - unist-util-generated "^1.1.0" - unist-util-position "^3.0.0" - unist-util-visit "^1.1.0" - xtend "^4.0.1" - -mdast-util-to-string@^1.0.0, mdast-util-to-string@^1.0.5: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" - integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== - -mdast-util-toc@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-3.1.0.tgz#395eeb877f067f9d2165d990d77c7eea6f740934" - integrity sha512-Za0hqL1PqWrvxGtA/3NH9D5nhGAUS9grMM4obEAz5+zsk1RIw/vWUchkaoDLNdrwk05A0CSC5eEXng36/1qE5w== - dependencies: - github-slugger "^1.2.1" - mdast-util-to-string "^1.0.5" - unist-util-is "^2.1.2" - unist-util-visit "^1.1.0" - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -8897,25 +7682,6 @@ metro@0.81.1, metro@^0.81.0: ws "^7.5.10" yargs "^17.6.2" -micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.5: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.7, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" @@ -8941,12 +7707,7 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -8996,19 +7757,11 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp@^0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -9026,26 +7779,6 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -module-deps-sortable@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/module-deps-sortable/-/module-deps-sortable-5.0.0.tgz#99db5bb08f7eab55e4c31f6b7c722c6a2144ba74" - integrity sha512-bnGGeghQmz/t/6771/KC4FmxpVm126iR6AAzzq4N6hVZQVl4+ZZBv+VF3PJmDyxXtVtgcgTSSP7NL+jq1QAHrg== - dependencies: - JSONStream "^1.0.3" - browser-resolve "^1.7.0" - cached-path-relative "^1.0.0" - concat-stream "~1.5.0" - defined "^1.0.0" - detective "^4.0.0" - duplexer2 "^0.1.2" - inherits "^2.0.1" - readable-stream "^2.0.2" - resolve "^1.1.3" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -9070,33 +7803,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.12.1: - version "2.22.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3" - integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== - nanoid@^3.1.23, nanoid@^3.3.7: version "3.3.8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -9129,11 +7840,6 @@ new-github-release-url@2.0.0: dependencies: type-fest "^2.5.1" -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -9176,37 +7882,11 @@ normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: semver "^7.3.4" validate-npm-package-license "^3.0.1" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== - dependencies: - remove-trailing-separator "^1.0.1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -now-and-later@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" - integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== - dependencies: - once "^1.3.2" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -9226,11 +7906,6 @@ nullthrows@^1.1.1: resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== - nwsapi@^2.2.2: version "2.2.16" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43" @@ -9243,20 +7918,11 @@ ob1@0.81.1: dependencies: flow-enums-runtime "^0.0.6" -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-inspect@^1.13.3: version "1.13.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" @@ -9267,14 +7933,7 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - -object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4, object.assign@^4.1.7: +object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4, object.assign@^4.1.7: version "4.1.7" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== @@ -9314,13 +7973,6 @@ object.groupby@^1.0.3: define-properties "^1.2.1" es-abstract "^1.23.2" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - object.values@^1.1.6, object.values@^1.2.0, object.values@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" @@ -9345,7 +7997,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -9433,22 +8085,6 @@ ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ordered-read-streams@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" - integrity sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw== - dependencies: - readable-stream "^2.0.1" - -os-locale@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-name@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-5.1.0.tgz#4f5ab5edfa6938b590112714f1570fe79f1d957a" @@ -9471,21 +8107,6 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -9598,27 +8219,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-entities@^1.0.2, parse-entities@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50" - integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg== - dependencies: - character-entities "^1.0.0" - character-entities-legacy "^1.0.0" - character-reference-invalid "^1.0.0" - is-alphanumerical "^1.0.0" - is-decimal "^1.0.0" - is-hexadecimal "^1.0.0" - -parse-filepath@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -9637,16 +8237,6 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-path@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.4.tgz#4bf424e6b743fb080831f03b536af9fc43f0ffea" - integrity sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw== - dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - qs "^6.9.4" - query-string "^6.13.8" - parse-path@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" @@ -9654,16 +8244,6 @@ parse-path@^7.0.0: dependencies: protocols "^2.0.0" -parse-url@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.5.tgz#4acab8982cef1846a0f8675fa686cef24b2f6f9b" - integrity sha512-e35AeLTSIlkw/5GFq70IN7po8fmDUjpDPY1rIK+VubRfsUvBonjQ+PBZG+vWMACnQSmNlvl524IucoDmcioMxA== - dependencies: - is-ssh "^1.3.0" - normalize-url "^6.1.0" - parse-path "^4.0.0" - protocols "^1.4.0" - parse-url@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" @@ -9683,16 +8263,6 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -9708,11 +8278,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -9728,18 +8293,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== - dependencies: - path-root-regex "^0.1.0" - path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -9777,7 +8330,7 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== -pify@^4.0.0, pify@^4.0.1: +pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== @@ -9815,11 +8368,6 @@ pod-install@^0.1.0: resolved "https://registry.yarnpkg.com/pod-install/-/pod-install-0.1.39.tgz#853a0585bafbd332c2ca6543854fd4919958cfb3" integrity sha512-0kVvdLYe0CtfJEr+ISvTMxAEB0UF4JMRToPjuu9xAAq1mEqA2Ql5u7uLWX1m45BMM+7NfU4LnBbnfNjmQE9GCw== -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -9875,16 +8423,11 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" -process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: +process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== - promise@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" @@ -9909,23 +8452,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -property-information@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-4.2.0.tgz#f0e66e07cbd6fed31d96844d958d153ad3eb486e" - integrity sha512-TlgDPagHh+eBKOnH2VYvk8qbwsCG/TAJdmTL7f1PROUcSO8qt/KSmShEQ/OKvock8X9tFjtqjCScyOkkkvIKVQ== - dependencies: - xtend "^4.0.1" - proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -protocols@^1.4.0: - version "1.4.8" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" - integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== - protocols@^2.0.0, protocols@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" @@ -9957,14 +8488,6 @@ psl@^1.1.33: dependencies: punycode "^2.3.1" -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" @@ -9973,15 +8496,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.5: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -10004,23 +8518,6 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@^6.4.0, qs@^6.9.4: - version "6.13.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" - integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== - dependencies: - side-channel "^1.0.6" - -query-string@^6.13.8: - version "6.14.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - query-string@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" @@ -10063,14 +8560,6 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" - integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== - dependencies: - bytes "1" - string_decoder "0.10" - rc@1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -10228,14 +8717,6 @@ read-pkg-up@^3.0.0: find-up "^2.0.0" read-pkg "^3.0.0" -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" - read-pkg "^3.0.0" - read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" @@ -10292,7 +8773,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -10305,40 +8786,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readable-stream@~2.1.0: - version "2.1.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" - integrity sha512-NkXT2AER7VKXeXtJNSaWLpWIhmtSE3K2PguaLEeWr4JILghcIKqoLt1A3wHrnpDC5+ekf8gfk1GKWkFXe4odMw== - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -10438,14 +8885,6 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexp.prototype.flags@^1.5.3: version "1.5.4" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" @@ -10531,132 +8970,6 @@ release-it@^17.3.0: wildcard-match "5.1.4" yargs-parser "21.1.1" -remark-html@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-8.0.0.tgz#9fcb859a6f3cb40f3ef15402950f1a62ec301b3a" - integrity sha512-3V2391GL3hxKhrkzYOyfPpxJ6taIKLCfuLVqumeWQOk3H9nTtSQ8St8kMYkBVIEAquXN1chT83qJ/2lAW+dpEg== - dependencies: - hast-util-sanitize "^1.0.0" - hast-util-to-html "^4.0.0" - mdast-util-to-hast "^3.0.0" - xtend "^4.0.1" - -remark-parse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" - integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA== - dependencies: - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^1.1.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^1.0.0" - vfile-location "^2.0.0" - xtend "^4.0.1" - -remark-reference-links@^4.0.1: - version "4.0.4" - resolved "https://registry.yarnpkg.com/remark-reference-links/-/remark-reference-links-4.0.4.tgz#190579a0d6b002859d6cdbdc5aeb8bbdae4e06ab" - integrity sha512-+2X8hwSQqxG4tvjYZNrTcEC+bXp8shQvwRGG6J/rnFTvBoU4G0BBviZoqKGZizLh/DG+0gSYhiDDWCqyxXW1iQ== - dependencies: - unist-util-visit "^1.0.0" - -remark-slug@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-5.1.2.tgz#715ecdef8df1226786204b1887d31ab16aa24609" - integrity sha512-DWX+Kd9iKycqyD+/B+gEFO3jjnt7Yg1O05lygYSNTe5i5PIxxxPjp5qPBDxPIzp5wreF7+1ROCwRgjEcqmzr3A== - dependencies: - github-slugger "^1.0.0" - mdast-util-to-string "^1.0.0" - unist-util-visit "^1.0.0" - -remark-stringify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba" - integrity sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w== - dependencies: - ccount "^1.0.0" - is-alphanumeric "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - longest-streak "^2.0.1" - markdown-escapes "^1.0.0" - markdown-table "^1.1.0" - mdast-util-compact "^1.0.0" - parse-entities "^1.0.2" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - stringify-entities "^1.0.1" - unherit "^1.0.4" - xtend "^4.0.1" - -remark-toc@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/remark-toc/-/remark-toc-5.1.1.tgz#8c229d6f834cdb43fde6685e2d43248d3fc82d78" - integrity sha512-vCPW4YOsm2CfyuScdktM9KDnJXVHJsd/ZeRtst+dnBU3B3KKvt8bc+bs5syJjyptAHfqo7H+5Uhz+2blWBfwow== - dependencies: - mdast-util-toc "^3.0.0" - remark-slug "^5.0.0" - -remark@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/remark/-/remark-9.0.0.tgz#c5cfa8ec535c73a67c4b0f12bfdbd3a67d8b2f60" - integrity sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A== - dependencies: - remark-parse "^5.0.0" - remark-stringify "^5.0.0" - unified "^6.0.0" - -remove-bom-buffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" - integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== - dependencies: - is-buffer "^1.1.5" - is-utf8 "^0.2.1" - -remove-bom-stream@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" - integrity sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA== - dependencies: - remove-bom-buffer "^3.0.0" - safe-buffer "^5.1.0" - through2 "^2.0.3" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.5.0, repeat-string@^1.5.4, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -replace-ext@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - integrity sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA== - -replace-ext@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" - integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -10667,11 +8980,6 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -10706,29 +9014,12 @@ resolve-global@1.0.0, resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" -resolve-options@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" - integrity sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A== - dependencies: - value-or-function "^3.0.0" - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - resolve.exports@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg== - -resolve@^1.1.3, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.8.1: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.4: version "1.22.10" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== @@ -10762,11 +9053,6 @@ restore-cursor@^5.0.0: onetime "^7.0.0" signal-exit "^4.1.0" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - retry@0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -10826,20 +9112,15 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-json-parse@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" - integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-push-apply@^1.0.0: version "1.0.0" @@ -10858,13 +9139,6 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -10904,7 +9178,7 @@ selfsigned@^2.4.1: "@types/node-forge" "^1.3.0" node-forge "^1" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -10972,11 +9246,6 @@ serve-static@^1.16.2: parseurl "~1.3.3" send "0.19.0" -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -11008,16 +9277,6 @@ set-proto@^1.0.0: es-errors "^1.3.0" es-object-atoms "^1.0.0" -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -11035,13 +9294,6 @@ shallowequal@1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -11049,11 +9301,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -11102,7 +9349,7 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.6, side-channel@^1.1.0: +side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== @@ -11113,7 +9360,7 @@ side-channel@^1.0.6, side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -11167,36 +9414,6 @@ smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - socks-proxy-agent@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" @@ -11219,17 +9436,6 @@ source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -11246,11 +9452,6 @@ source-map-support@^0.5.16, source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - source-map@0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -11266,11 +9467,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -space-separated-tokens@^1.0.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" - integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== - spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -11302,13 +9498,6 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - split2@^3.0.0, split2@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -11376,19 +9565,6 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -11404,31 +9580,11 @@ stdin-discarder@^0.2.2: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stream-array@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/stream-array/-/stream-array-1.1.2.tgz#9e5f7345f2137c30ee3b498b9114e80b52bb7eb5" - integrity sha512-1yWdVsMEm/btiMa2YyHiC3mDrtAqlmNNaDRylx2F7KHhm3C4tA6kSR2V9mpeMthv+ujvbl8Kamyh5xaHHdFvyQ== - dependencies: - readable-stream "~2.1.0" - stream-buffers@2.2.x: version "2.2.0" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - integrity sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw== - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - -stream-shift@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" - integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== - strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -11455,29 +9611,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -string-template@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" - integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^2.0.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11563,11 +9697,6 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@0.10, string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -11582,30 +9711,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -stringify-entities@^1.0.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7" - integrity sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A== - dependencies: - character-entities-html4 "^1.0.0" - character-entities-legacy "^1.0.0" - is-alphanumerical "^1.0.0" - is-hexadecimal "^1.0.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -11630,11 +9735,6 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -11659,16 +9759,16 @@ strip-indent@^4.0.0: dependencies: min-indent "^1.0.1" -strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + stubborn-fs@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/stubborn-fs/-/stubborn-fs-1.2.5.tgz#e5e244223166921ddf66ed5e062b6b3bf285bfd2" @@ -11694,13 +9794,6 @@ stylis@4.3.2: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.2.tgz#8f76b70777dd53eb669c6f58c997bf0a9972e444" integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg== -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - integrity sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg== - dependencies: - minimist "^1.1.0" - sucrase@3.34.0: version "3.34.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" @@ -11721,13 +9814,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -11815,14 +9901,7 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== -through2-filter@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.1.0.tgz#4a1b45d2b76b3ac93ec137951e372c268efc1a4e" - integrity sha512-VhZsTsfrIJjyUi6GeecnwcOJlmoqgIdGFDjqnV5ape+F1DN8GejfPO66XyIhoinxmxGImiUTrq9RwpTN5yszGA== - dependencies: - through2 "^4.0.2" - -through2@^2.0.0, through2@^2.0.3: +through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -11830,7 +9909,7 @@ through2@^2.0.0, through2@^2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through2@^4.0.0, through2@^4.0.2: +through2@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== @@ -11847,18 +9926,6 @@ tiny-invariant@^1.3.3: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== -tiny-lr@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" - integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== - dependencies: - body "^5.1.0" - debug "^3.1.0" - faye-websocket "~0.10.0" - livereload-js "^2.3.0" - object-assign "^4.1.0" - qs "^6.4.0" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -11876,29 +9943,6 @@ tmpl@1.0.5: resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== -to-absolute-glob@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" - integrity sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA== - dependencies: - is-absolute "^1.0.0" - is-negated-glob "^1.0.0" - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -11906,23 +9950,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -to-through@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" - integrity sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q== - dependencies: - through2 "^2.0.3" - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -11945,11 +9972,6 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" -trim-lines@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.3.tgz#839514be82428fd9e7ec89e35081afe8f6f93115" - integrity sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA== - trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -11965,21 +9987,6 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw== -trim-trailing-lines@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" - integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== - -trough@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" - integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== - ts-api-utils@^1.0.1: version "1.4.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" @@ -12204,11 +10211,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typedarray@~0.0.5: - version "0.0.7" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.7.tgz#799207136a37f3b3efb8c66c40010d032714dc73" - integrity sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ== - "typescript@^4.6.4 || ^5.2.2", typescript@^5.1.3, typescript@^5.2.2: version "5.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" @@ -12239,14 +10241,6 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" @@ -12275,96 +10269,6 @@ unicorn-magic@^0.1.0: resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== -unified@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" - integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-plain-obj "^1.1.0" - trough "^1.0.0" - vfile "^2.0.0" - x-is-string "^0.1.0" - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unique-stream@^2.0.2: - version "2.3.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" - integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== - dependencies: - json-stable-stringify-without-jsonify "^1.0.1" - through2-filter "^3.0.0" - -unist-builder@^1.0.1, unist-builder@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.4.tgz#e1808aed30bd72adc3607f25afecebef4dd59e17" - integrity sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg== - dependencies: - object-assign "^4.1.0" - -unist-util-generated@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" - integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== - -unist-util-is@^2.0.0, unist-util-is@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.3.tgz#459182db31f4742fceaea88d429693cbf0043d20" - integrity sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA== - -unist-util-is@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" - integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== - -unist-util-position@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" - integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== - -unist-util-remove-position@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020" - integrity sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A== - dependencies: - unist-util-visit "^1.1.0" - -unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" - integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ== - -unist-util-stringify-position@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" - integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== - dependencies: - "@types/unist" "^2.0.2" - -unist-util-visit-parents@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" - integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== - dependencies: - unist-util-is "^3.0.0" - -unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" - integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== - dependencies: - unist-util-visit-parents "^2.0.0" - universal-user-agent@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa" @@ -12385,19 +10289,6 @@ unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - update-browserslist-db@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" @@ -12429,11 +10320,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - url-join@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-5.0.0.tgz#c2f1e5cbd95fa91082a93b58a1f42fecb4bdbcf1" @@ -12452,11 +10338,6 @@ use-latest-callback@^0.2.1: resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16" integrity sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ== -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -12494,134 +10375,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -value-or-function@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" - integrity sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg== - -vfile-location@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" - integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== - -vfile-message@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1" - integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA== - dependencies: - unist-util-stringify-position "^1.1.1" - -vfile-message@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" - integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== - dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position "^2.0.0" - -vfile-reporter@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/vfile-reporter/-/vfile-reporter-6.0.2.tgz#cbddaea2eec560f27574ce7b7b269822c191a676" - integrity sha512-GN2bH2gs4eLnw/4jPSgfBjo+XCuvnX9elHICJZjVD4+NM0nsUrMTvdjGY5Sc/XG69XVTgLwj7hknQVc6M9FukA== - dependencies: - repeat-string "^1.5.0" - string-width "^4.0.0" - supports-color "^6.0.0" - unist-util-stringify-position "^2.0.0" - vfile-sort "^2.1.2" - vfile-statistics "^1.1.0" - -vfile-sort@^2.1.0, vfile-sort@^2.1.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/vfile-sort/-/vfile-sort-2.2.2.tgz#720fe067ce156aba0b411a01bb0dc65596aa1190" - integrity sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA== - -vfile-statistics@^1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-1.1.4.tgz#b99fd15ecf0f44ba088cc973425d666cb7a9f245" - integrity sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA== - -vfile@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a" - integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w== - dependencies: - is-buffer "^1.1.4" - replace-ext "1.0.0" - unist-util-stringify-position "^1.0.0" - vfile-message "^1.0.0" - -vfile@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" - integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== - dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^2.0.0" - vfile-message "^2.0.0" - -vinyl-fs@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" - integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== - dependencies: - fs-mkdirp-stream "^1.0.0" - glob-stream "^6.1.0" - graceful-fs "^4.0.0" - is-valid-glob "^1.0.0" - lazystream "^1.0.0" - lead "^1.0.0" - object.assign "^4.0.4" - pumpify "^1.3.5" - readable-stream "^2.3.3" - remove-bom-buffer "^3.0.0" - remove-bom-stream "^1.2.0" - resolve-options "^1.1.0" - through2 "^2.0.0" - to-through "^2.0.0" - value-or-function "^3.0.0" - vinyl "^2.0.0" - vinyl-sourcemap "^1.1.0" - -vinyl-sourcemap@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" - integrity sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA== - dependencies: - append-buffer "^1.0.2" - convert-source-map "^1.5.0" - graceful-fs "^4.1.6" - normalize-path "^2.1.1" - now-and-later "^2.0.0" - remove-bom-buffer "^3.0.0" - vinyl "^2.0.0" - -vinyl@^2.0.0, vinyl@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" - integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== - dependencies: - clone "^2.1.1" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - vlq@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== -vue-template-compiler@^2.5.16: - version "2.7.16" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz#c81b2d47753264c77ac03b9966a46637482bb03b" - integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ== - dependencies: - de-indent "^1.0.2" - he "^1.2.0" - w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" @@ -12653,20 +10411,6 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" @@ -12737,11 +10481,6 @@ which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - which-typed-array@^1.1.16, which-typed-array@^1.1.18: version "1.1.18" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" @@ -12754,13 +10493,6 @@ which-typed-array@^1.1.16, which-typed-array@^1.1.18: gopd "^1.2.0" has-tostringtag "^1.0.2" -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -12797,14 +10529,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -12879,11 +10603,6 @@ ws@^8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -x-is-string@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" - integrity sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w== - xcode@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/xcode/-/xcode-3.0.1.tgz#3efb62aac641ab2c702458f9a0302696146aa53c" @@ -12930,16 +10649,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -"y18n@^3.2.1 || ^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -12965,37 +10679,11 @@ yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^12.0.2: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"