From e68b7afa701cb10712e4ffd20176116306e3bfec Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 11:15:57 -0500 Subject: [PATCH 01/36] =?UTF-8?q?docs:=20comprehensive=20code=20audit=20?= =?UTF-8?q?=E2=80=94=2020=20issues=20found=20across=20iOS,=20Android,=20an?= =?UTF-8?q?d=20JS=20layers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUDIT.md | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 AUDIT.md diff --git a/AUDIT.md b/AUDIT.md new file mode 100644 index 00000000..cf53e497 --- /dev/null +++ b/AUDIT.md @@ -0,0 +1,186 @@ +# 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. + +### 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 From 8ab870457fb52aa9104b3c40cf5e1589ee0a110d Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 11:16:35 -0500 Subject: [PATCH 02/36] =?UTF-8?q?docs:=20add=20finding=20#21=20=E2=80=94?= =?UTF-8?q?=20disconnect=20event=20always=20null=20error=20(Codex=20Androi?= =?UTF-8?q?d=20review)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUDIT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AUDIT.md b/AUDIT.md index cf53e497..695f8f8e 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -136,6 +136,10 @@ Static mutable state shared across instances. `clear()` in `destroyClient()` inv 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. + ### 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). From efca6825208bb79bcf69557ca4f799e9634751c7 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 11:17:21 -0500 Subject: [PATCH 03/36] =?UTF-8?q?docs:=20complete=20audit=20=E2=80=94=2025?= =?UTF-8?q?=20issues=20from=205=20review=20agents=20(3=20Claude=20+=202=20?= =?UTF-8?q?Codex)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUDIT.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/AUDIT.md b/AUDIT.md index 695f8f8e..cad92661 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -140,6 +140,34 @@ Global JVM-wide setting that overwrites any other library's error handler. `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). From 5ae15672079bc3a13194672403347ac7bc08fbdd Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 11:48:03 -0500 Subject: [PATCH 04/36] docs: v4 TurboModule rewrite spec (rev 2, Codex-reviewed) Fixes from Codex review: - Use Codegen typed events, not RCTEventEmitter (legacy) - iOS: @objc adapter over Swift actors (actors not TurboModule surface) - Background BLE with state restoration is supported (not dropped) - Rich error model with platform diagnostics (gattStatus, attErrorCode, isRetryable) - Event batching: per-stream rules, bypass for timing-sensitive, ordering contract - Android mock testing: custom MockBleManager (Nordic mock utils not in v2.x) - Support floor: RN 0.82+ (legacy arch removed) - Additional test cases: permission revoke, BT off, bond failure, restoration --- .../2026-03-15-v4-turbomodule-rewrite.md | 566 ++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 docs/specs/2026-03-15-v4-turbomodule-rewrite.md 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 00000000..d4c0555b --- /dev/null +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -0,0 +1,566 @@ +# 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** (`NativeBleModule.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 + - Handles scan throttle heuristics (5 starts per 30s window) + - BLE 5.0 extended advertising scan support +- **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** for operation queue serialization (internal only) +- **Thin `@objc` adapter** exposed to TurboModule — actors are NOT the TurboModule surface directly. The adapter bridges between the Codegen-generated ObjC++ interface and the internal Swift actor. Per [RN docs](https://reactnative.dev/docs/0.79/the-new-architecture/turbo-modules-with-swift). + - `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) | +| `requestConnectionParameters(deviceId, params)` | Connection interval, latency, timeout | +| `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, + 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. 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 +// TurboModule entry point +@objc(BlePlx) +class BlePlx: RCTEventEmitter { + private var bleActor: BleActor? + + @objc func createClient(_ restoreId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + // Invalidate previous instance if exists + bleActor?.invalidate() + bleActor = BleActor(queue: methodQueue, restoreId: restoreId) + bleActor?.delegate = self + resolve(nil) + } +} + +// Swift actor for thread-safe BLE operations +actor BleActor { + private let centralManager: CBCentralManager + 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 operation queue +actor PeripheralWrapper { + private let peripheral: CBPeripheral + private let delegateHandler: PeripheralDelegate + + func discoverServices(_ uuids: [CBUUID]?) async throws -> [CBService] { ... } + func readCharacteristic(_ characteristic: CBCharacteristic) async throws -> Data { ... } + func writeCharacteristic(_ characteristic: CBCharacteristic, data: Data, type: CBCharacteristicWriteType) async throws { ... } + func setNotify(_ enabled: Bool, for characteristic: CBCharacteristic) async throws { ... } + + // L2CAP + func openL2CAPChannel(psm: CBL2CAPPSM) async throws -> CBL2CAPChannel { ... } + + // PHY (iOS doesn't expose PHY selection — document as Android-only) +} +``` + +### 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 +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 parameters +await manager.requestConnectionParameters(deviceId, { + minInterval: 15, // ms + maxInterval: 30, // ms + latency: 0, + timeout: 4000, // ms +}); + +// L2CAP (iOS only) +const channel = await manager.openL2CAPChannel(deviceId, 0x0080); +``` + +--- + +## 10. File Structure + +``` +react-native-ble-plx/ +├── src/ ← TypeScript API + TurboModule spec +│ ├── NativeBleModule.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.swift ← TurboModule entry point +│ ├── 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()` | From fa3550e6d580cc0af02aa12c62792f60740e0298 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 11:49:13 -0500 Subject: [PATCH 05/36] =?UTF-8?q?docs:=20v4=20spec=20rev=203=20=E2=80=94?= =?UTF-8?q?=20add=20connection=20state=20machine,=20Codegen=20spec,=20bond?= =?UTF-8?q?ing/L2CAP/PHY=20error=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-03-15-v4-turbomodule-rewrite.md | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md index d4c0555b..f5d9c9d9 100644 --- a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -179,6 +179,23 @@ export const BleErrorCode = { // 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; ``` @@ -210,7 +227,114 @@ interface BleError { --- -## 5. Android Implementation Details +## 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 (`NativeBleModule.ts`) + +The Codegen spec is the contract between JS and native. Key shape: + +```typescript +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + // Lifecycle + createClient(restoreStateIdentifier?: string): Promise; + destroyClient(): Promise; + + // State + state(): Promise; + onStateChange(callback: (state: string) => void): void; + + // Scanning + startDeviceScan(uuids: string[] | null, options: Object | null): void; + stopDeviceScan(): Promise; + + // Connection + connectToDevice(deviceId: string, options?: Object): Promise; + cancelDeviceConnection(deviceId: string): Promise; + isDeviceConnected(deviceId: string): Promise; + + // Discovery + discoverAllServicesAndCharacteristicsForDevice(deviceId: string, transactionId?: string): Promise; + + // Read/Write + readCharacteristicForDevice(deviceId: string, serviceUUID: string, characteristicUUID: string, transactionId?: string): Promise; + writeCharacteristicForDevice(deviceId: string, serviceUUID: string, characteristicUUID: string, value: string, withResponse: boolean, transactionId?: string): Promise; + + // Monitor + monitorCharacteristicForDevice(deviceId: string, serviceUUID: string, characteristicUUID: string, transactionId?: string, options?: Object): void; + + // MTU / PHY / Connection Parameters + requestMTUForDevice(deviceId: string, mtu: number, transactionId?: string): Promise; + requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise; + readPhy(deviceId: string): Promise; + requestConnectionParameters(deviceId: string, params: Object): Promise; + + // L2CAP (iOS only — Android rejects with OperationNotSupported) + openL2CAPChannel(deviceId: string, psm: number): Promise; + + // Bonding + getBondedDevices(): Promise; + + // Authorization (iOS) + getAuthorizationStatus(): Promise; + + // Cancellation + cancelTransaction(transactionId: string): Promise; + + // Events (typed, emitted via Codegen event emitter) + // - ScanEvent: { device: Object } + // - ConnectionStateEvent: { deviceId: string, state: string, error?: Object } + // - CharacteristicValueEvent: { deviceId: string, serviceUUID: string, characteristicUUID: string, value: string, transactionId?: string } + // - StateChangeEvent: { state: string } + // - RestoreStateEvent: { devices: Object[] } +} + +export default TurboModuleRegistry.getEnforcing('BlePlx'); +``` + +This generates the native bindings. The actual event types are defined via the [typed native module events](https://reactnative.dev/docs/0.79/the-new-architecture/native-modules-custom-events) pattern. + +--- + +## 6. Android Implementation Details ### Dependencies From 0e502a2c97ccfab9bb3901924e36f1a318fa0ba8 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 12:07:49 -0500 Subject: [PATCH 06/36] =?UTF-8?q?docs:=20v4=20spec=20rev=204=20=E2=80=94?= =?UTF-8?q?=20major=20Codegen=20+=20iOS=20architecture=20fixes=20from=20re?= =?UTF-8?q?search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Research-driven fixes: - Codegen spec rewritten: NativeBlePlx.ts with inline Readonly<{}> types, CodegenTypes.EventEmitter for all streaming events, no Object escape hatches - codegenConfig added for package.json - TurboModuleRegistry.get (nullable) instead of getEnforcing - iOS: custom actor executor on CB DispatchQueue (Swift 5.9+) for non-Sendable CB objects - iOS: ObjC++ .mm entry point (pure Swift TurboModules not possible) - iOS: @preconcurrency import CoreBluetooth for Swift 6 - iOS: GATT operation queue (CB does NOT serialize operations) - requestConnectionParameters → requestConnectionPriority (coarse, Android-only) - Scanner Compat does NOT handle throttling — built-in debouncing required - Auto-MTU 517 on Android connect (fixes #1 user-reported issue) - L2CAP expanded: open/write/close lifecycle, dynamic PSMs only - Device identifier incompatibility documented (MAC vs opaque UUID) - onStateChange as EventEmitter (not callback — callbacks are single-fire) - Background scan on iOS: must specify service UUID filters --- docs/ble-mobile-research.md | 300 ++++++++++++++++++ .../2026-03-15-v4-turbomodule-rewrite.md | 267 ++++++++++++---- 2 files changed, 502 insertions(+), 65 deletions(-) create mode 100644 docs/ble-mobile-research.md diff --git a/docs/ble-mobile-research.md b/docs/ble-mobile-research.md new file mode 100644 index 00000000..58298e19 --- /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/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md index f5d9c9d9..5559a2fe 100644 --- a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -79,8 +79,10 @@ Background BLE with state restoration is **supported** (not dropped — it's a v - Bonding state management as Kotlin Flow - Connection lifecycle with retry - **Nordic Scanner Compat Library** for scanning - - Handles scan throttle heuristics (5 starts per 30s window) + - 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) - **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 @@ -89,8 +91,16 @@ Background BLE with state restoration is **supported** (not dropped — it's a v ### iOS Native (Swift + ObjC++ adapter) - **Direct CoreBluetooth** with custom async/await wrapper -- **Swift actor** for operation queue serialization (internal only) -- **Thin `@objc` adapter** exposed to TurboModule — actors are NOT the TurboModule surface directly. The adapter bridges between the Codegen-generated ObjC++ interface and the internal Swift actor. Per [RN docs](https://reactnative.dev/docs/0.79/the-new-architecture/turbo-modules-with-swift). +- **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) @@ -122,7 +132,7 @@ Background BLE with state restoration is **supported** (not dropped — it's a v | `requestPhy(deviceId, txPhy, rxPhy)` | BLE 5.0 PHY selection | | `readPhy(deviceId)` | Read current PHY | | `openL2CAPChannel(deviceId, psm)` | iOS L2CAP channel (iOS only) | -| `requestConnectionParameters(deviceId, params)` | Connection interval, latency, timeout | +| `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 | @@ -267,70 +277,173 @@ interface BleError { ## 5a. Codegen Spec (`NativeBleModule.ts`) -The Codegen spec is the contract between JS and native. Key shape: +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 } from 'react-native'; +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): Promise; + createClient(restoreStateIdentifier: string | null): Promise; destroyClient(): Promise; // State state(): Promise; - onStateChange(callback: (state: string) => void): void; - // Scanning - startDeviceScan(uuids: string[] | null, options: Object | null): void; + // Scanning (scan throttle debouncing built-in) + startDeviceScan(uuids: ReadonlyArray | null, optionsJson: string): void; stopDeviceScan(): Promise; - // Connection - connectToDevice(deviceId: string, options?: Object): Promise; - cancelDeviceConnection(deviceId: string): Promise; + // Connection (auto-MTU 517 on Android) + connectToDevice(deviceId: string, optionsJson: string): Promise; + cancelDeviceConnection(deviceId: string): Promise; isDeviceConnected(deviceId: string): Promise; // Discovery - discoverAllServicesAndCharacteristicsForDevice(deviceId: string, transactionId?: string): Promise; + discoverAllServicesAndCharacteristics(deviceId: string, transactionId: string | null): Promise; // Read/Write - readCharacteristicForDevice(deviceId: string, serviceUUID: string, characteristicUUID: string, transactionId?: string): Promise; - writeCharacteristicForDevice(deviceId: string, serviceUUID: string, characteristicUUID: string, value: string, withResponse: boolean, transactionId?: string): 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; - // Monitor - monitorCharacteristicForDevice(deviceId: string, serviceUUID: string, characteristicUUID: string, transactionId?: string, options?: Object): void; + // Monitor (use EventEmitter, not callback — callbacks are single-fire) + monitorCharacteristic(deviceId: string, serviceUuid: string, characteristicUuid: string, transactionId: string | null): void; - // MTU / PHY / Connection Parameters - requestMTUForDevice(deviceId: string, mtu: number, transactionId?: string): Promise; - requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise; - readPhy(deviceId: string): Promise; - requestConnectionParameters(deviceId: string, params: Object): Promise; + // MTU + requestMtu(deviceId: string, mtu: number, transactionId: string | null): Promise; - // L2CAP (iOS only — Android rejects with OperationNotSupported) - openL2CAPChannel(deviceId: string, psm: number): Promise; + // PHY (Android only — iOS returns current PHY info but cannot set) + requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise; + readPhy(deviceId: string): Promise; - // Bonding - getBondedDevices(): 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; - // Authorization (iOS) + // 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; - // Events (typed, emitted via Codegen event emitter) - // - ScanEvent: { device: Object } - // - ConnectionStateEvent: { deviceId: string, state: string, error?: Object } - // - CharacteristicValueEvent: { deviceId: string, serviceUUID: string, characteristicUUID: string, value: string, transactionId?: string } - // - StateChangeEvent: { state: string } - // - RestoreStateEvent: { devices: Object[] } + // ─── 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; } -export default TurboModuleRegistry.getEnforcing('BlePlx'); +export default TurboModuleRegistry.get('NativeBlePlx'); ``` -This generates the native bindings. The actual event types are defined via the [typed native module events](https://reactnative.dev/docs/0.79/the-new-architecture/native-modules-custom-events) pattern. +**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. --- @@ -404,24 +517,34 @@ Nordic BLE Library handles the forced MTU=517 behavior. We document it and expos ### Architecture ```swift -// TurboModule entry point -@objc(BlePlx) -class BlePlx: RCTEventEmitter { - private var bleActor: BleActor? +// 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 func createClient(_ restoreId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - // Invalidate previous instance if exists + @objc public func createClient(_ restoreId: String?) async throws { bleActor?.invalidate() - bleActor = BleActor(queue: methodQueue, restoreId: restoreId) - bleActor?.delegate = self - resolve(nil) + bleActor = BLEActor(restoreId: restoreId) } } -// Swift actor for thread-safe BLE operations -actor BleActor { - private let centralManager: CBCentralManager - private let delegateHandler: CentralManagerDelegate +// 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 { ... } @@ -429,20 +552,37 @@ actor BleActor { func disconnect(peripheralId: UUID) async throws { ... } } -// Per-peripheral operation queue +// Per-peripheral GATT operation queue (also an actor on the BLE queue) actor PeripheralWrapper { - private let peripheral: CBPeripheral + 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 { ... } - func writeCharacteristic(_ characteristic: CBCharacteristic, data: Data, type: CBCharacteristicWriteType) async throws { ... } - func setNotify(_ enabled: Bool, for characteristic: CBCharacteristic) async throws { ... } - // L2CAP - func openL2CAPChannel(psm: CBL2CAPPSM) async throws -> CBL2CAPChannel { ... } + 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) + } + } - // PHY (iOS doesn't expose PHY selection — document as Android-only) + // 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 { ... } } ``` @@ -603,13 +743,9 @@ sub.remove(); // Properly cleans up native + JS listeners // PHY selection (Android only) await manager.requestPhy(deviceId, PhyType.LE_2M, PhyType.LE_2M); -// Connection parameters -await manager.requestConnectionParameters(deviceId, { - minInterval: 15, // ms - maxInterval: 30, // ms - latency: 0, - timeout: 4000, // ms -}); +// 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); @@ -640,7 +776,8 @@ react-native-ble-plx/ │ ├── EventSerializer.kt ← Native → JS event conversion │ └── ErrorConverter.kt ← GATT error → unified error code ├── ios/ -│ ├── BlePlx.swift ← TurboModule entry point +│ ├── 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 From c211be295206b712c00e40993f62f16933a26cae Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 12:08:56 -0500 Subject: [PATCH 07/36] =?UTF-8?q?docs:=20v4=20spec=20rev=205=20=E2=80=94?= =?UTF-8?q?=20final=20gaps:=20connection=20events,=20background=20BLE=20pl?= =?UTF-8?q?atforms,=20bonding=20contract,=20restore=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-03-15-v4-turbomodule-rewrite.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md index 5559a2fe..f61065b9 100644 --- a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -428,6 +428,9 @@ export interface Spec extends TurboModule { readonly onStateChange: CodegenTypes.EventEmitter; readonly onRestoreState: CodegenTypes.EventEmitter; readonly onError: CodegenTypes.EventEmitter; + readonly onConnectionEvent: CodegenTypes.EventEmitter>; // iOS 13+ connection events + readonly onL2CAPData: CodegenTypes.EventEmitter>; // L2CAP incoming data + readonly onL2CAPClose: CodegenTypes.EventEmitter>; } export default TurboModuleRegistry.get('NativeBlePlx'); @@ -447,6 +450,33 @@ export default TurboModuleRegistry.get('NativeBlePlx'); --- +## 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 @@ -612,6 +642,9 @@ func monitorCharacteristic(_ characteristic: CBCharacteristic) -> AsyncStream Date: Sun, 15 Mar 2026 12:16:57 -0500 Subject: [PATCH 08/36] =?UTF-8?q?docs:=20v4=20spec=20rev=206=20=E2=80=94?= =?UTF-8?q?=20Gemini=20review=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix filename inconsistency: NativeBlePlx.ts throughout (was NativeBleModule.ts in header) - Replace optionsJson string with typed Readonly<{}> for scan and connect options - Android 14 auto-MTU caveat: skip requestMtu if system already negotiated >= 517 - Add neverForLocation manifest flag requirement - Note for impl: transactionId→Nordic Request mapping, bonding vs encryption race, L2CAP StreamDelegate bridge, concurrent write+indicate stress test needed --- .../2026-03-15-v4-turbomodule-rewrite.md | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md index f61065b9..b0b102d5 100644 --- a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -65,7 +65,7 @@ Background BLE with state restoration is **supported** (not dropped — it's a v ### JS/TS Layer -- **TurboModule Codegen spec** (`NativeBleModule.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+. +- **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 @@ -82,7 +82,8 @@ Background BLE with state restoration is **supported** (not dropped — it's a v - 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) +- **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 @@ -275,7 +276,7 @@ interface BleError { --- -## 5a. Codegen Spec (`NativeBleModule.ts`) +## 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). @@ -377,11 +378,22 @@ export interface Spec extends TurboModule { state(): Promise; // Scanning (scan throttle debouncing built-in) - startDeviceScan(uuids: ReadonlyArray | null, optionsJson: string): void; + 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, optionsJson: string): Promise; + 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; @@ -791,7 +803,7 @@ const channel = await manager.openL2CAPChannel(deviceId, 0x0080); ``` react-native-ble-plx/ ├── src/ ← TypeScript API + TurboModule spec -│ ├── NativeBleModule.ts ← Codegen spec +│ ├── NativeBlePlx.ts ← Codegen spec │ ├── BleManager.ts ← Main API class │ ├── Device.ts │ ├── Characteristic.ts From 2ec80296bbb283a5075211c59d31d19e4dae606f Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 12:30:12 -0500 Subject: [PATCH 09/36] =?UTF-8?q?docs:=20v4=20spec=20rev=207=20=E2=80=94?= =?UTF-8?q?=20add=20iOS=20state=20restoration=20bootstrap=20timing=20note?= =?UTF-8?q?=20(Codex=20final=20validation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/specs/2026-03-15-v4-turbomodule-rewrite.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md index b0b102d5..c50164b2 100644 --- a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -654,6 +654,12 @@ func monitorCharacteristic(_ characteristic: CBCharacteristic) -> AsyncStream Date: Sun, 15 Mar 2026 12:51:14 -0500 Subject: [PATCH 10/36] =?UTF-8?q?fix(plans):=20address=20Codex=20review=20?= =?UTF-8?q?=E2=80=94=20Codegen=20verification,=20GATT=20queue=20signature,?= =?UTF-8?q?=20L2CAP=20wiring,=20connection=20events,=20task=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-03-15-plan-a-turbomodule-js-api.md | 528 ++++++++++++++++++ .../plans/2026-03-15-plan-b-android-native.md | 255 +++++++++ docs/plans/2026-03-15-plan-c-ios-native.md | 359 ++++++++++++ docs/plans/2026-03-15-plan-d-testing.md | 227 ++++++++ 4 files changed, 1369 insertions(+) create mode 100644 docs/plans/2026-03-15-plan-a-turbomodule-js-api.md create mode 100644 docs/plans/2026-03-15-plan-b-android-native.md create mode 100644 docs/plans/2026-03-15-plan-c-ios-native.md create mode 100644 docs/plans/2026-03-15-plan-d-testing.md 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 00000000..30f2889d --- /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 00000000..ccb4da3a --- /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 00000000..c79b401f --- /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 00000000..f514f2a7 --- /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" +``` From 92e435b742d53b1d58c791f74e368701795f0daa Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 13:17:45 -0500 Subject: [PATCH 11/36] docs: add subscriptionType, getMtu, and onBondStateChange to v4 Codegen spec Add subscriptionType parameter to monitorCharacteristic to allow explicit indicate vs notify selection (null = auto-detect, preferring notify). Add getMtu(deviceId) method to read current negotiated MTU. Add onBondStateChange event emitter for bond state transitions (none/bonding/bonded). --- docs/specs/2026-03-15-v4-turbomodule-rewrite.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md index c50164b2..c9fdc75e 100644 --- a/docs/specs/2026-03-15-v4-turbomodule-rewrite.md +++ b/docs/specs/2026-03-15-v4-turbomodule-rewrite.md @@ -405,9 +405,12 @@ export interface Spec extends TurboModule { writeCharacteristic(deviceId: string, serviceUuid: string, characteristicUuid: string, value: string, withResponse: boolean, transactionId: string | null): Promise; // Monitor (use EventEmitter, not callback — callbacks are single-fire) - monitorCharacteristic(deviceId: string, serviceUuid: string, characteristicUuid: string, transactionId: string | null): void; + // 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) @@ -440,6 +443,7 @@ export interface Spec extends TurboModule { 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>; From 35876e9e013aa566bae8e287ba00f6923ea6de8e Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 13:36:53 -0500 Subject: [PATCH 12/36] feat: TurboModule Codegen spec with typed EventEmitters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add src/specs/NativeBlePlx.ts — the Codegen contract for the v4 TurboModule rewrite. Defines all typed interfaces (DeviceInfo, CharacteristicInfo, ScanResult, etc.), method signatures, and ten typed CodegenTypes.EventEmitter declarations. Uses TurboModuleRegistry.get (nullable) per spec. Add codegenConfig to package.json pointing at src/specs with Android javaPackageName com.bleplx. --- package.json | 8 ++ src/specs/NativeBlePlx.ts | 151 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/specs/NativeBlePlx.ts diff --git a/package.json b/package.json index af6c9028..0fc8d556 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,14 @@ } } }, + "codegenConfig": { + "name": "NativeBlePlxSpec", + "type": "modules", + "jsSrcsDir": "src/specs", + "android": { + "javaPackageName": "com.bleplx" + } + }, "react-native-builder-bob": { "source": "src", "output": "lib", diff --git a/src/specs/NativeBlePlx.ts b/src/specs/NativeBlePlx.ts new file mode 100644 index 00000000..4452f86e --- /dev/null +++ b/src/specs/NativeBlePlx.ts @@ -0,0 +1,151 @@ +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'); From e372b2c2fbd54723c12ab1bebadd5ae9655abddd Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 13:40:51 -0500 Subject: [PATCH 13/36] feat: add EventBatcher with configurable interval, max batch size, and immediate mode --- __tests__/BleError.test.ts | 27 ++++++++++++ __tests__/EventBatcher.test.ts | 42 ++++++++++++++++++ jest.config.js | 3 +- src/BleError.ts | 78 ++++++++++++++++++++++++++++++++++ src/EventBatcher.ts | 42 ++++++++++++++++++ src/types.ts | 51 ++++++++++++++++++++++ 6 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 __tests__/BleError.test.ts create mode 100644 __tests__/EventBatcher.test.ts create mode 100644 src/BleError.ts create mode 100644 src/EventBatcher.ts create mode 100644 src/types.ts diff --git a/__tests__/BleError.test.ts b/__tests__/BleError.test.ts new file mode 100644 index 00000000..7f1553ee --- /dev/null +++ b/__tests__/BleError.test.ts @@ -0,0 +1,27 @@ +// __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__/EventBatcher.test.ts b/__tests__/EventBatcher.test.ts new file mode 100644 index 00000000..59799a71 --- /dev/null +++ b/__tests__/EventBatcher.test.ts @@ -0,0 +1,42 @@ +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); +}); diff --git a/jest.config.js b/jest.config.js index bb1e79cb..13defbd1 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/src/BleError.ts b/src/BleError.ts new file mode 100644 index 00000000..30e1e400 --- /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/EventBatcher.ts b/src/EventBatcher.ts new file mode 100644 index 00000000..4dc956c4 --- /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.flush(); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..5c22274e --- /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; +} From fc8785519616c886a2c3465e4dcfc803ff8b38b0 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 13:48:17 -0500 Subject: [PATCH 14/36] feat: typescript types and unified error model add types.ts with State, LogLevel, ConnectionPriority, ConnectionState const objects and ScanOptions/ConnectOptions interfaces add BleError.ts with BleErrorCode const object and BleError class for unified cross-platform error handling add __tests__/BleError.test.ts with constructor and code value tests fix tooling for typescript support in src/ and __tests__/ directories --- .eslintrc.json | 21 +++- .prettierrc | 8 +- __tests__/BleError.test.ts | 31 ++--- __tests__/EventBatcher.test.ts | 66 +++++----- jest.config.js | 2 +- lefthook.yml | 2 +- src/BleError.ts | 78 ++++++------ src/EventBatcher.ts | 34 ++--- src/specs/NativeBlePlx.ts | 223 ++++++++++++++++++--------------- src/types.ts | 42 +++---- tsconfig.json | 2 +- 11 files changed, 279 insertions(+), 230 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 031b5b1e..2f8e4c60 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,7 @@ }, "overrides": [ { - "files": ["src/**", "__tests__/**", "integration-tests/**"], + "files": ["src/**/*.js", "src/**/*.jsx", "__tests__/**/*.js", "__tests__/**/*.jsx", "integration-tests/**/*.js", "integration-tests/**/*.jsx"], "parser": "hermes-eslint", "extends": [ "plugin:react/recommended", @@ -37,7 +37,7 @@ } }, { - "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 +77,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 +93,18 @@ "prettier/prettier": "off" } }, + { + "files": ["src/specs/**"], + "rules": { + "import/no-default-export": "off" + } + }, + { + "files": ["__tests__/**/*.ts", "__tests__/**/*.tsx"], + "rules": { + "@typescript-eslint/no-non-null-assertion": "off" + } + }, { "files": ["plugin/**"], "rules": { diff --git a/.prettierrc b/.prettierrc index 9ec8574e..4844e023 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/__tests__/BleError.test.ts b/__tests__/BleError.test.ts index 7f1553ee..29f882f9 100644 --- a/__tests__/BleError.test.ts +++ b/__tests__/BleError.test.ts @@ -1,5 +1,5 @@ // __tests__/BleError.test.ts -import { BleError, BleErrorCode } from '../src/BleError'; +import { BleError, BleErrorCode } from '../src/BleError' test('BleError constructs with all fields', () => { const err = new BleError({ @@ -10,18 +10,21 @@ test('BleError constructs with all fields', () => { 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); -}); + 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); -}); + expect(BleErrorCode.DeviceNotFound).toBe(0) + expect(BleErrorCode.ManagerNotInitialized).toBe(400) + expect(BleErrorCode.UnknownError).toBe(999) +}) diff --git a/__tests__/EventBatcher.test.ts b/__tests__/EventBatcher.test.ts index 59799a71..4900bf9f 100644 --- a/__tests__/EventBatcher.test.ts +++ b/__tests__/EventBatcher.test.ts @@ -1,42 +1,42 @@ -import { EventBatcher } from '../src/EventBatcher'; +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(); -}); + 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]); +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(); + expect(received[0]!).toEqual([1, 2, 3]) + batcher.dispose() + done() } - }); - batcher.push(1); - batcher.push(2); - batcher.push(3); + }) + batcher.push(1) + batcher.push(2) + batcher.push(3) // Events should not be delivered yet - expect(received).toEqual([]); -}); + expect(received).toEqual([]) +}) -test('max batch size caps delivery', (done) => { - const received: number[][] = []; - const batcher = new EventBatcher(50, 3, (batch) => { - received.push([...batch]); +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(); + expect(received[0]!.length).toBeLessThanOrEqual(3) + batcher.dispose() + done() } - }); - for (let i = 0; i < 10; i++) batcher.push(i); -}); + }) + for (let i = 0; i < 10; i += 1) batcher.push(i) +}) diff --git a/jest.config.js b/jest.config.js index 13defbd1..3f633ca9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ module.exports = { roots: ['/__tests__'], preset: 'react-native', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] } diff --git a/lefthook.yml b/lefthook.yml index 9e3c91c5..513f5f5d 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/src/BleError.ts b/src/BleError.ts index 30e1e400..14835446 100644 --- a/src/BleError.ts +++ b/src/BleError.ts @@ -30,49 +30,49 @@ export const BleErrorCode = { PhyNegotiationFailed: 700, ScanFailed: 800, ScanThrottled: 801, - UnknownError: 999, -} as const; -export type BleErrorCode = typeof BleErrorCode[keyof typeof BleErrorCode]; + 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; + 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; + 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; + 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/EventBatcher.ts b/src/EventBatcher.ts index 4dc956c4..31a0107e 100644 --- a/src/EventBatcher.ts +++ b/src/EventBatcher.ts @@ -1,42 +1,42 @@ export class EventBatcher { - private buffer: T[] = []; - private timer: ReturnType | null = null; - private disposed = false; + private buffer: T[] = [] + private timer: ReturnType | null = null + private disposed = false constructor( private intervalMs: number, private maxBatchSize: number, - private onBatch: (events: T[]) => void, + private onBatch: (events: T[]) => void ) { if (intervalMs > 0) { - this.timer = setInterval(() => this.flush(), intervalMs); + this.timer = setInterval(() => this.flush(), intervalMs) } } push(event: T): void { - if (this.disposed) return; + if (this.disposed) return if (this.intervalMs === 0) { - this.onBatch([event]); - return; + this.onBatch([event]) + return } - this.buffer.push(event); + this.buffer.push(event) if (this.buffer.length >= this.maxBatchSize) { - this.flush(); + this.flush() } } flush(): void { - if (this.buffer.length === 0) return; - const batch = this.buffer.splice(0, this.maxBatchSize); - this.onBatch(batch); + if (this.buffer.length === 0) return + const batch = this.buffer.splice(0, this.maxBatchSize) + this.onBatch(batch) } dispose(): void { - this.disposed = true; + this.disposed = true if (this.timer) { - clearInterval(this.timer); - this.timer = null; + clearInterval(this.timer) + this.timer = null } - this.flush(); + this.flush() } } diff --git a/src/specs/NativeBlePlx.ts b/src/specs/NativeBlePlx.ts index 4452f86e..b1f90829 100644 --- a/src/specs/NativeBlePlx.ts +++ b/src/specs/NativeBlePlx.ts @@ -1,151 +1,176 @@ -import type { TurboModule, CodegenTypes } from 'react-native'; -import { TurboModuleRegistry } from 'react-native'; +import type { TurboModule } from 'react-native' +import type * as CodegenTypes from 'react-native/Libraries/Types/CodegenTypes' +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 -}>; + 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; -}>; + 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; -}>; + 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; -}>; + 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; -}>; + 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' -}>; + state: string // 'Unknown' | 'Resetting' | 'Unsupported' | 'Unauthorized' | 'PoweredOff' | 'PoweredOn' +}> export type RestoreStateEvent = Readonly<{ - devices: ReadonlyArray; -}>; + 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; -}>; + 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; + createClient(restoreStateIdentifier: string | null): Promise + destroyClient(): Promise // State - state(): Promise; + state(): Promise // Scanning - startDeviceScan(uuids: ReadonlyArray | null, options: Readonly<{ - scanMode?: number; - callbackType?: number; - legacyScan?: boolean; - allowDuplicates?: boolean; - }> | null): void; - stopDeviceScan(): Promise; + 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; + 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; + 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; + 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; + 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; + 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; + requestPhy(deviceId: string, txPhy: number, rxPhy: number): Promise + readPhy(deviceId: string): Promise // Connection Priority - requestConnectionPriority(deviceId: string, priority: number): Promise; + requestConnectionPriority(deviceId: string, priority: number): Promise // L2CAP - openL2CAPChannel(deviceId: string, psm: number): Promise>; - writeL2CAPChannel(channelId: number, data: string): Promise; - closeL2CAPChannel(channelId: number): Promise; + openL2CAPChannel(deviceId: string, psm: number): Promise> + writeL2CAPChannel(channelId: number, data: string): Promise + closeL2CAPChannel(channelId: number): Promise // Bonding - getBondedDevices(): Promise>; + getBondedDevices(): Promise> // Authorization - getAuthorizationStatus(): Promise; + getAuthorizationStatus(): Promise // Cancellation - cancelTransaction(transactionId: string): Promise; + 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>; + 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'); +export default TurboModuleRegistry.get('NativeBlePlx') diff --git a/src/types.ts b/src/types.ts index 5c22274e..2792c14d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,9 +6,9 @@ export const State = { Unsupported: 'Unsupported', Unauthorized: 'Unauthorized', PoweredOff: 'PoweredOff', - PoweredOn: 'PoweredOn', -} as const; -export type State = typeof State[keyof typeof State]; + PoweredOn: 'PoweredOn' +} as const +export type State = (typeof State)[keyof typeof State] export const LogLevel = { None: 'None', @@ -16,36 +16,36 @@ export const LogLevel = { Debug: 'Debug', Info: 'Info', Warning: 'Warning', - Error: 'Error', -} as const; -export type LogLevel = typeof LogLevel[keyof typeof LogLevel]; + 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]; + 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]; + Disconnecting: 'disconnecting' +} as const +export type ConnectionState = (typeof ConnectionState)[keyof typeof ConnectionState] export interface ScanOptions { - scanMode?: number; - callbackType?: number; - legacyScan?: boolean; - allowDuplicates?: boolean; + scanMode?: number + callbackType?: number + legacyScan?: boolean + allowDuplicates?: boolean } export interface ConnectOptions { - autoConnect?: boolean; - timeout?: number; - retries?: number; - retryDelay?: number; - requestMtu?: number; + autoConnect?: boolean + timeout?: number + retries?: number + retryDelay?: number + requestMtu?: number } diff --git a/tsconfig.json b/tsconfig.json index 02de3e9c..a849a880 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/**"] } From af3297beb44840059d291c731c06b8552878e2c3 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 13:50:24 -0500 Subject: [PATCH 15/36] fix: restore standard CodegenTypes import, separate tsconfig for Codegen specs --- .eslintrc.json | 3 +++ src/specs/NativeBlePlx.ts | 3 +-- tsconfig.json | 2 +- tsconfig.specs.json | 15 +++++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 tsconfig.specs.json diff --git a/.eslintrc.json b/.eslintrc.json index 2f8e4c60..8279a262 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -95,6 +95,9 @@ }, { "files": ["src/specs/**"], + "parserOptions": { + "project": ["./tsconfig.specs.json"] + }, "rules": { "import/no-default-export": "off" } diff --git a/src/specs/NativeBlePlx.ts b/src/specs/NativeBlePlx.ts index b1f90829..a4799ba0 100644 --- a/src/specs/NativeBlePlx.ts +++ b/src/specs/NativeBlePlx.ts @@ -1,5 +1,4 @@ -import type { TurboModule } from 'react-native' -import type * as CodegenTypes from 'react-native/Libraries/Types/CodegenTypes' +import type { TurboModule, CodegenTypes } from 'react-native' import { TurboModuleRegistry } from 'react-native' // All types must be inline — Codegen ignores imports from other files diff --git a/tsconfig.json b/tsconfig.json index a849a880..00467a80 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "target": "esnext", "verbatimModuleSyntax": true }, - "exclude": ["integration-tests/**", "example/**", "example-expo/**"] + "exclude": ["integration-tests/**", "example/**", "example-expo/**", "src/specs/**"] } diff --git a/tsconfig.specs.json b/tsconfig.specs.json new file mode 100644 index 00000000..2bfd6f34 --- /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"] +} From a6780aa7ea0d64cc77a3c253042138db113fdd37 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 13:55:20 -0500 Subject: [PATCH 16/36] feat: add BleManager API wrapping TurboModule with subscription management --- src/BleManager.ts | 468 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 src/BleManager.ts diff --git a/src/BleManager.ts b/src/BleManager.ts new file mode 100644 index 00000000..b02f7102 --- /dev/null +++ b/src/BleManager.ts @@ -0,0 +1,468 @@ +import { TurboModuleRegistry } from 'react-native' +import type { EventSubscription, TurboModule } from 'react-native' +import type { State, ScanOptions, ConnectOptions, ConnectionPriority } from './types' +import { BleError } 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 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 + + readonly onScanResult: NativeEventEmitter + readonly onConnectionStateChange: NativeEventEmitter + readonly onCharacteristicValueUpdate: NativeEventEmitter + readonly onStateChange: NativeEventEmitter + readonly onError: NativeEventEmitter +} + +// --------------------------------------------------------------------------- +// Public types +// --------------------------------------------------------------------------- + +export type { DeviceInfo, CharacteristicInfo, ScanResult, ConnectionStateEvent, CharacteristicValueEvent } + +export interface Subscription { + remove(): void +} + +export interface MonitorOptions { + transactionId?: string + batchInterval?: 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 nativeModule: NativeBlePlxSpec + 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() + + constructor() { + this.nativeModule = getNativeModule() + } + + // ----------------------------------------------------------------------- + // Lifecycle + // ----------------------------------------------------------------------- + + async createClient(restoreStateIdentifier: string | null = null): Promise { + 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() + + // 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 { + const sub = this.nativeModule.onStateChange(event => { + callback(event.state as State) + }) + + if (emitCurrentState) { + this.nativeModule.state().then( + s => callback(s as State), + () => { + // Ignore errors when reading initial state + } + ) + } + + return { + remove: () => { + sub.remove() + } + } + } + + // ----------------------------------------------------------------------- + // 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 (100ms interval, max 50 per batch) + this.scanBatcher = new EventBatcher(100, 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) + this.nativeModule.startDeviceScan(serviceUuids, options ?? null) + } + + 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 { + return this.nativeModule.connectToDevice(deviceId, options ?? null) + } + + 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: 'ios', + deviceId: event.deviceId + }) + callback(bleError, event) + } else { + callback(null, event) + } + }) + + return { + remove: () => { + sub.remove() + } + } + } + + // ----------------------------------------------------------------------- + // 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 + + 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, null, 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) + } + + // ----------------------------------------------------------------------- + // Connection Priority + // ----------------------------------------------------------------------- + + async requestConnectionPriority(deviceId: string, priority: ConnectionPriority): Promise { + return this.nativeModule.requestConnectionPriority(deviceId, priority) + } + + // ----------------------------------------------------------------------- + // Cancellation + // ----------------------------------------------------------------------- + + async cancelTransaction(transactionId: string): Promise { + this.activeTransactionIds.delete(transactionId) + await this.nativeModule.cancelTransaction(transactionId) + } +} From 530b61c0899519b1126bfbaf6f063cffd06e0d3b Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:04:29 -0500 Subject: [PATCH 17/36] feat: add Device/Service/Characteristic/Descriptor wrappers and public index exports --- .eslintignore | 3 ++ package.json | 2 +- src/Characteristic.ts | 69 +++++++++++++++++++++++++++++++++++++++++++ src/Descriptor.ts | 15 ++++++++++ src/Device.ts | 58 ++++++++++++++++++++++++++++++++++++ src/Service.ts | 9 ++++++ src/index.ts | 17 +++++++++++ tsconfig.json | 2 +- 8 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/Characteristic.ts create mode 100644 src/Descriptor.ts create mode 100644 src/Device.ts create mode 100644 src/Service.ts create mode 100644 src/index.ts diff --git a/.eslintignore b/.eslintignore index 68b1c204..b9385f45 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,6 @@ node_modules/ docs/** plugin/build lib/** +src/index.d.ts +src/*.js +__tests__/*.js diff --git a/package.json b/package.json index 0fc8d556..0b1b003e 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "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}\" && documentation lint index.js && yarn typecheck", "prepack": "bob build", "release": "release-it", "example": "yarn --cwd example", diff --git a/src/Characteristic.ts b/src/Characteristic.ts new file mode 100644 index 00000000..f2bc4fb8 --- /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.ts b/src/Descriptor.ts new file mode 100644 index 00000000..5144fbe7 --- /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.ts b/src/Device.ts new file mode 100644 index 00000000..8a08639a --- /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/Service.ts b/src/Service.ts new file mode 100644 index 00000000..486d1514 --- /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/index.ts b/src/index.ts new file mode 100644 index 00000000..c3bb8eaa --- /dev/null +++ b/src/index.ts @@ -0,0 +1,17 @@ +export { BleManager } from './BleManager' +export type { + Subscription, + MonitorOptions, + DeviceInfo, + CharacteristicInfo, + ScanResult, + ConnectionStateEvent, + CharacteristicValueEvent +} 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/tsconfig.json b/tsconfig.json index 00467a80..846f3002 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "target": "esnext", "verbatimModuleSyntax": true }, - "exclude": ["integration-tests/**", "example/**", "example-expo/**", "src/specs/**"] + "exclude": ["integration-tests/**", "example/**", "example-expo/**", "src/specs/**", "src/index.d.ts", "__tests__/**"] } From b45afdeef1f42561f671fb84fe0850e149acea5c Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:07:30 -0500 Subject: [PATCH 18/36] chore: update deps, version 4.0.0-alpha.0, require RN 0.82+ --- package.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0b1b003e..13311ebe 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", @@ -88,7 +88,6 @@ "@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", @@ -127,7 +126,7 @@ }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": ">= 0.82.0" }, "engines": { "node": ">= 18.0.0" From 5f3de8759955e32747b412dd25f91f516f60d0d9 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:07:54 -0500 Subject: [PATCH 19/36] =?UTF-8?q?fix:=20expo=20plugin=20=E2=80=94=20proper?= =?UTF-8?q?=20manifest=20for=20AGP=20namespace=20mode,=20neverForLocation?= =?UTF-8?q?=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AndroidManifestNew.xml was empty (bare ``), breaking AGP namespace mode manifest merging; add xmlns:android and xmlns:tools declarations - Default neverForLocation to true so BLUETOOTH_SCAN gets android:usesPermissionFlags="neverForLocation" out of the box, matching the library's own AndroidManifest.xml; apps that derive location from BLE can opt out by setting neverForLocation: false in their Expo config --- android/src/main/AndroidManifestNew.xml | 4 +++- plugin/src/withBLE.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/android/src/main/AndroidManifestNew.xml b/android/src/main/AndroidManifestNew.xml index 0bfa73ba..c5f96182 100644 --- a/android/src/main/AndroidManifestNew.xml +++ b/android/src/main/AndroidManifestNew.xml @@ -1 +1,3 @@ - + + diff --git a/plugin/src/withBLE.ts b/plugin/src/withBLE.ts index 8a614e1a..59585425 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( From 05b2ec85d4b73e9c491be1b65163975bd61a0167 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:15:47 -0500 Subject: [PATCH 20/36] feat(android): kotlin TurboModule with Nordic BLE Library Replace the v3 Java/RxJava implementation with a Kotlin TurboModule architecture using Nordic Android-BLE-Library 2.11.0 for GATT operations and Scanner Compat 1.6.0 for scanning. - BlePlxModule extends Codegen-generated NativeBlePlxSpec - BleManagerWrapper: per-device Nordic BleManager with coroutine suspend APIs - ScanManager: Scanner Compat with 5-starts/30s throttle protection - ErrorConverter: GATT status codes mapped to unified BleErrorCode - EventSerializer: native objects serialized to Codegen event types - PermissionHelper: runtime checks for API 31+ permission model - Fixed AndroidManifestNew.xml (was empty in v3) - Removed all v3 Java source files under android/src/main/java/ --- android/build.gradle | 69 +- android/src/main/AndroidManifestNew.xml | 16 + .../main/java/com/bleplx/BlePlxModule.java | 1123 ------------ .../main/java/com/bleplx/BlePlxPackage.java | 28 - android/src/main/java/com/bleplx/Event.java | 16 - .../com/bleplx/adapter/AdvertisementData.java | 251 --- .../java/com/bleplx/adapter/BleAdapter.java | 254 --- .../com/bleplx/adapter/BleAdapterCreator.java | 7 - .../com/bleplx/adapter/BleAdapterFactory.java | 21 - .../java/com/bleplx/adapter/BleModule.java | 1612 ----------------- .../com/bleplx/adapter/Characteristic.java | 153 -- .../com/bleplx/adapter/ConnectionOptions.java | 72 - .../com/bleplx/adapter/ConnectionState.java | 12 - .../java/com/bleplx/adapter/Descriptor.java | 117 -- .../main/java/com/bleplx/adapter/Device.java | 95 - .../com/bleplx/adapter/OnErrorCallback.java | 8 - .../com/bleplx/adapter/OnEventCallback.java | 6 - .../com/bleplx/adapter/OnSuccessCallback.java | 6 - .../com/bleplx/adapter/RefreshGattMoment.java | 20 - .../java/com/bleplx/adapter/ScanResult.java | 132 -- .../main/java/com/bleplx/adapter/Service.java | 58 - .../com/bleplx/adapter/errors/BleError.java | 32 - .../bleplx/adapter/errors/BleErrorCode.java | 57 - .../bleplx/adapter/errors/BleErrorUtils.java | 86 - .../bleplx/adapter/errors/ErrorConverter.java | 219 --- .../CannotMonitorCharacteristicException.java | 15 - .../bleplx/adapter/utils/Base64Converter.java | 13 - .../com/bleplx/adapter/utils/ByteUtils.java | 15 - .../com/bleplx/adapter/utils/Constants.java | 66 - .../bleplx/adapter/utils/DisposableMap.java | 39 - .../com/bleplx/adapter/utils/IdGenerator.java | 22 - .../bleplx/adapter/utils/IdGeneratorKey.java | 37 - .../com/bleplx/adapter/utils/LogLevel.java | 47 - .../utils/RefreshGattCustomOperation.java | 50 - .../bleplx/adapter/utils/SafeExecutor.java | 33 - .../bleplx/adapter/utils/ServiceFactory.java | 16 - .../bleplx/adapter/utils/UUIDConverter.java | 51 - .../mapper/RxBleDeviceToDeviceMapper.java | 19 - .../RxScanResultToScanResultMapper.java | 22 - .../BleErrorToJsObjectConverter.java | 65 - .../CharacteristicToJsObjectConverter.java | 46 - .../DescriptorToJsObjectConverter.java | 39 - .../converter/DeviceToJsObjectConverter.java | 66 - .../bleplx/converter/JSObjectConverter.java | 17 - .../ScanResultToJsObjectConverter.java | 102 -- .../converter/ServiceToJsObjectConverter.java | 26 - .../com/bleplx/utils/Base64Converter.java | 13 - .../java/com/bleplx/utils/ErrorDefaults.java | 9 - .../bleplx/utils/ReadableArrayConverter.java | 28 - .../java/com/bleplx/utils/SafePromise.java | 59 - .../java/com/bleplx/utils/UUIDConverter.java | 70 - .../kotlin/com/bleplx/BleManagerWrapper.kt | 298 +++ .../main/kotlin/com/bleplx/BlePlxModule.kt | 788 ++++++++ .../main/kotlin/com/bleplx/BlePlxPackage.kt | 35 + .../main/kotlin/com/bleplx/ErrorConverter.kt | 210 +++ .../main/kotlin/com/bleplx/EventSerializer.kt | 168 ++ .../kotlin/com/bleplx/PermissionHelper.kt | 73 + .../src/main/kotlin/com/bleplx/ScanManager.kt | 163 ++ 58 files changed, 1774 insertions(+), 5416 deletions(-) delete mode 100644 android/src/main/java/com/bleplx/BlePlxModule.java delete mode 100644 android/src/main/java/com/bleplx/BlePlxPackage.java delete mode 100644 android/src/main/java/com/bleplx/Event.java delete mode 100644 android/src/main/java/com/bleplx/adapter/AdvertisementData.java delete mode 100644 android/src/main/java/com/bleplx/adapter/BleAdapter.java delete mode 100644 android/src/main/java/com/bleplx/adapter/BleAdapterCreator.java delete mode 100644 android/src/main/java/com/bleplx/adapter/BleAdapterFactory.java delete mode 100755 android/src/main/java/com/bleplx/adapter/BleModule.java delete mode 100755 android/src/main/java/com/bleplx/adapter/Characteristic.java delete mode 100644 android/src/main/java/com/bleplx/adapter/ConnectionOptions.java delete mode 100644 android/src/main/java/com/bleplx/adapter/ConnectionState.java delete mode 100644 android/src/main/java/com/bleplx/adapter/Descriptor.java delete mode 100755 android/src/main/java/com/bleplx/adapter/Device.java delete mode 100644 android/src/main/java/com/bleplx/adapter/OnErrorCallback.java delete mode 100644 android/src/main/java/com/bleplx/adapter/OnEventCallback.java delete mode 100644 android/src/main/java/com/bleplx/adapter/OnSuccessCallback.java delete mode 100755 android/src/main/java/com/bleplx/adapter/RefreshGattMoment.java delete mode 100644 android/src/main/java/com/bleplx/adapter/ScanResult.java delete mode 100755 android/src/main/java/com/bleplx/adapter/Service.java delete mode 100755 android/src/main/java/com/bleplx/adapter/errors/BleError.java delete mode 100755 android/src/main/java/com/bleplx/adapter/errors/BleErrorCode.java delete mode 100755 android/src/main/java/com/bleplx/adapter/errors/BleErrorUtils.java delete mode 100755 android/src/main/java/com/bleplx/adapter/errors/ErrorConverter.java delete mode 100755 android/src/main/java/com/bleplx/adapter/exceptions/CannotMonitorCharacteristicException.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/Base64Converter.java delete mode 100644 android/src/main/java/com/bleplx/adapter/utils/ByteUtils.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/Constants.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/DisposableMap.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/IdGenerator.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/IdGeneratorKey.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/LogLevel.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/RefreshGattCustomOperation.java delete mode 100644 android/src/main/java/com/bleplx/adapter/utils/SafeExecutor.java delete mode 100644 android/src/main/java/com/bleplx/adapter/utils/ServiceFactory.java delete mode 100755 android/src/main/java/com/bleplx/adapter/utils/UUIDConverter.java delete mode 100644 android/src/main/java/com/bleplx/adapter/utils/mapper/RxBleDeviceToDeviceMapper.java delete mode 100644 android/src/main/java/com/bleplx/adapter/utils/mapper/RxScanResultToScanResultMapper.java delete mode 100644 android/src/main/java/com/bleplx/converter/BleErrorToJsObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/converter/CharacteristicToJsObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/converter/DescriptorToJsObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/converter/DeviceToJsObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/converter/JSObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/converter/ScanResultToJsObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/converter/ServiceToJsObjectConverter.java delete mode 100644 android/src/main/java/com/bleplx/utils/Base64Converter.java delete mode 100644 android/src/main/java/com/bleplx/utils/ErrorDefaults.java delete mode 100644 android/src/main/java/com/bleplx/utils/ReadableArrayConverter.java delete mode 100644 android/src/main/java/com/bleplx/utils/SafePromise.java delete mode 100644 android/src/main/java/com/bleplx/utils/UUIDConverter.java create mode 100644 android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt create mode 100644 android/src/main/kotlin/com/bleplx/BlePlxModule.kt create mode 100644 android/src/main/kotlin/com/bleplx/BlePlxPackage.kt create mode 100644 android/src/main/kotlin/com/bleplx/ErrorConverter.kt create mode 100644 android/src/main/kotlin/com/bleplx/EventSerializer.kt create mode 100644 android/src/main/kotlin/com/bleplx/PermissionHelper.kt create mode 100644 android/src/main/kotlin/com/bleplx/ScanManager.kt diff --git a/android/build.gradle b/android/build.gradle index 4f1fd5d1..6832d49d 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,16 @@ 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' } -if (isNewArchitectureEnabled()) { - react { - jsRootDir = file("../src/") - libraryName = "BlePlx" - codegenJavaPackageName = "com.bleplx" - } +react { + jsRootDir = file("../src/") + libraryName = "BlePlx" + codegenJavaPackageName = "com.bleplx" } diff --git a/android/src/main/AndroidManifestNew.xml b/android/src/main/AndroidManifestNew.xml index c5f96182..0af9cc4d 100644 --- a/android/src/main/AndroidManifestNew.xml +++ b/android/src/main/AndroidManifestNew.xml @@ -1,3 +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 d4ef9c5e..00000000 --- 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 5ed13336..00000000 --- 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 cadcde88..00000000 --- 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 21a6d51f..00000000 --- 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 07f0995d..00000000 --- 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 93223f14..00000000 --- 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 57fc3a63..00000000 --- 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 44392b65..00000000 --- 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 7208813a..00000000 --- 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 51b0044c..00000000 --- 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 56e0ca93..00000000 --- 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 5057497d..00000000 --- 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 253042be..00000000 --- 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 a073aff4..00000000 --- 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 bc6330c8..00000000 --- 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 27a39691..00000000 --- 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 eafefc07..00000000 --- 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 53eac5a8..00000000 --- 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 b23eaa2b..00000000 --- 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 802528c8..00000000 --- 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 fe8a511d..00000000 --- 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 998647c9..00000000 --- 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 96bb3c66..00000000 --- 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 bc94b7f8..00000000 --- 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 814fe773..00000000 --- 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 111ec29a..00000000 --- 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 048cefe9..00000000 --- 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 83e94729..00000000 --- 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 116124e1..00000000 --- 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 721b9f6f..00000000 --- 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 eb249fd1..00000000 --- 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 68f7fff0..00000000 --- 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 fa19f4e3..00000000 --- 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 b4b2f9f0..00000000 --- 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 96247f8c..00000000 --- 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 8e943e19..00000000 --- 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 2cf44175..00000000 --- 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 370f1352..00000000 --- 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 270b4f62..00000000 --- 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 05af9acb..00000000 --- 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 1c5acdb6..00000000 --- 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 d72130c0..00000000 --- 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 1482f029..00000000 --- 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 68853233..00000000 --- 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 2f5b0d37..00000000 --- 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 b58ab840..00000000 --- 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 2aa5987d..00000000 --- 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 8d86f731..00000000 --- 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 d36ad63e..00000000 --- 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 00000000..64f8c07c --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt @@ -0,0 +1,298 @@ +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. + */ +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)) + } + + // 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) + .with(callback) + .fail { _, status -> + close(GattException(status, "Enable indications failed for $characteristicUuid")) + } + .enqueue() + } else if (hasNotify) { + enableNotifications(char) + .with(callback) + .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 00000000..1b3f9da3 --- /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 00000000..56eb8dfe --- /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 00000000..5fad20e1 --- /dev/null +++ b/android/src/main/kotlin/com/bleplx/ErrorConverter.kt @@ -0,0 +1,210 @@ +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_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 00000000..5b901792 --- /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 00000000..45e650e7 --- /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 00000000..08bfacec --- /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()) + } +} From e6653532925128213c66f6515591e5fbe422ff6d Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:17:32 -0500 Subject: [PATCH 21/36] feat(ios): swift turbomodule with actor-based coreBluetooth --- ios/BLEActor.swift | 439 ++++++++++++++++++++++++ ios/BLEModuleImpl.swift | 563 +++++++++++++++++++++++++++++++ ios/BlePlx-Bridging-Header.h | 3 + ios/BlePlx-Swift.h | 444 ------------------------ ios/BlePlx.h | 15 - ios/BlePlx.m | 510 ---------------------------- ios/BlePlx.mm | 245 ++++++++++++++ ios/CentralManagerDelegate.swift | 195 +++++++++++ ios/ErrorConverter.swift | 296 ++++++++++++++++ ios/EventSerializer.swift | 182 ++++++++++ ios/GATTOperationQueue.swift | 110 ++++++ ios/L2CAPManager.swift | 210 ++++++++++++ ios/PeripheralDelegate.swift | 250 ++++++++++++++ ios/PeripheralWrapper.swift | 207 ++++++++++++ ios/ScanManager.swift | 66 ++++ ios/StateRestoration.swift | 74 ++++ react-native-ble-plx.podspec | 36 +- 17 files changed, 2851 insertions(+), 994 deletions(-) create mode 100644 ios/BLEActor.swift create mode 100644 ios/BLEModuleImpl.swift delete mode 100644 ios/BlePlx-Swift.h delete mode 100644 ios/BlePlx.h delete mode 100644 ios/BlePlx.m create mode 100644 ios/BlePlx.mm create mode 100644 ios/CentralManagerDelegate.swift create mode 100644 ios/ErrorConverter.swift create mode 100644 ios/EventSerializer.swift create mode 100644 ios/GATTOperationQueue.swift create mode 100644 ios/L2CAPManager.swift create mode 100644 ios/PeripheralDelegate.swift create mode 100644 ios/PeripheralWrapper.swift create mode 100644 ios/ScanManager.swift create mode 100644 ios/StateRestoration.swift diff --git a/ios/BLEActor.swift b/ios/BLEActor.swift new file mode 100644 index 00000000..f0b1d51e --- /dev/null +++ b/ios/BLEActor.swift @@ -0,0 +1,439 @@ +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 = DispatchQueue(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 dict in delegateHandler.restorationStream { + guard let self = self else { break } + 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 { + 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) + await wrapper.openL2CAPChannel(psm: CBL2CAPPSM(psm)) + // The actual channel is received via the peripheral delegate + // For now, return a pending channel ID + // TODO: Wire up CBPeripheral didOpenL2CAPChannel callback + throw BleError(code: .l2capOpenFailed, message: "L2CAP channel open is pending — callback not yet wired") + } + + 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 00000000..b94005d8 --- /dev/null +++ b/ios/BLEModuleImpl.swift @@ -0,0 +1,563 @@ +import Foundation +@preconcurrency import CoreBluetooth + +/// Protocol for the event emitter (ObjC++ BlePlx module) +@objc public protocol BLEEventEmitter: AnyObject { + func sendEvent(withName name: String, body: Any?) +} + +// MARK: - Event names + +private enum EventName { + static let scanResult = "onScanResult" + static let connectionStateChange = "onConnectionStateChange" + static let characteristicValueUpdate = "onCharacteristicValueUpdate" + static let stateChange = "onStateChange" + static let restoreState = "onRestoreState" + static let error = "onError" + static let bondStateChange = "onBondStateChange" + static let connectionEvent = "onConnectionEvent" + static let l2capData = "onL2CAPData" + static let l2capClose = "onL2CAPClose" +} + +// 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 var eventEmitterAdapter: EventEmitterAdapter? + private var eventEmitter: BLEEventEmitter? { eventEmitterAdapter } + private var actor: BLEActor? + + @objc public init(eventEmitter: RCTEventEmitter) { + self.eventEmitterAdapter = EventEmitterAdapter(emitter: eventEmitter) + super.init() + } + + @objc public static func supportedEventNames() -> [String] { + return [ + EventName.scanResult, + EventName.connectionStateChange, + EventName.characteristicValueUpdate, + EventName.stateChange, + EventName.restoreState, + EventName.error, + EventName.bondStateChange, + EventName.connectionEvent, + EventName.l2capData, + EventName.l2capClose, + ] + } + + // MARK: - Private helpers + + private func sendEvent(_ name: String, body: Any?) { + eventEmitter?.sendEvent(withName: name, body: body) + } + + private func rejectWithError(_ reject: @escaping RCTPromiseRejectBlock, error: BleError) { + reject(String(error.code.rawValue), error.message, nil) + sendEvent(EventName.error, body: error.toDictionary()) + } + + private func rejectWithError(_ reject: @escaping RCTPromiseRejectBlock, 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + let emitter = self + + actor = BLEActor( + onScanResult: { [weak emitter] snapshot in + emitter?.sendEvent(EventName.scanResult, body: snapshot.toDictionary()) + }, + 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?.sendEvent(EventName.connectionStateChange, body: body) + }, + 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?.sendEvent(EventName.characteristicValueUpdate, body: body) + }, + onStateChange: { [weak emitter] state in + emitter?.sendEvent(EventName.stateChange, body: ["state": state]) + }, + onRestoreState: { [weak emitter] devices in + let deviceDicts = devices.map { $0.toDictionary() } + emitter?.sendEvent(EventName.restoreState, body: ["devices": deviceDicts]) + }, + onError: { [weak emitter] error in + emitter?.sendEvent(EventName.error, body: error.toDictionary()) + }, + onL2CAPData: { [weak emitter] channelId, data in + emitter?.sendEvent(EventName.l2capData, body: ["channelId": channelId, "data": data]) + }, + onL2CAPClose: { [weak emitter] channelId, error in + emitter?.sendEvent(EventName.l2capClose, body: ["channelId": channelId, "error": error as Any]) + } + ) + + Task { + await actor?.createClient(restoreStateIdentifier: restoreStateIdentifier) + resolve(nil) + } + } + + @objc public func destroyClient( + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + Task { + await actor?.stopDeviceScan() + resolve(nil) + } + } + + // MARK: - Connection + + @objc public func connectToDevice( + deviceId: String, + options: NSDictionary?, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 { + sendEvent(EventName.error, body: bleError.toDictionary()) + } + } + } + } + + // MARK: - MTU + + @objc public func getMtu( + deviceId: String, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + // 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + // 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + // iOS doesn't expose bonded device list via CoreBluetooth + resolve([]) + } + + // MARK: - Authorization + + @objc public func getAuthorizationStatus( + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + 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 RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + Task { + await actor?.cancelTransaction(transactionId) + resolve(nil) + } + } +} + +// MARK: - RCTEventEmitter adapter + +/// Wraps an RCTEventEmitter (from ObjC) to conform to BLEEventEmitter. +/// Since RCTEventEmitter is an ObjC class, we use a thin wrapper. +class EventEmitterAdapter: BLEEventEmitter { + private weak var emitter: RCTEventEmitter? + + init(emitter: RCTEventEmitter) { + self.emitter = emitter + } + + func sendEvent(withName name: String, body: Any?) { + emitter?.sendEvent(withName: name, body: body) + } +} diff --git a/ios/BlePlx-Bridging-Header.h b/ios/BlePlx-Bridging-Header.h index 1b2cb5d6..37eaae0f 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 20882ee3..00000000 --- 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 9127ca96..00000000 --- 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 8e0da36b..00000000 --- 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 00000000..c425ae8d --- /dev/null +++ b/ios/BlePlx.mm @@ -0,0 +1,245 @@ +// +// BlePlx.mm +// react-native-ble-plx +// +// TurboModule entry point — ObjC++ bridge to Swift implementation +// + +#import +#import + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#endif + +// Auto-generated Swift bridge header +#if __has_include("react_native_ble_plx-Swift.h") +#import "react_native_ble_plx-Swift.h" +#else +#import +#endif + +#ifdef RCT_NEW_ARCH_ENABLED +@interface BlePlx : NativeBlePlxSpec +@end +#else +#import +#import +@interface BlePlx : RCTEventEmitter +@end +#endif + +@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)invalidate { + [_impl invalidate]; + [super invalidate]; +} + +- (NSArray *)supportedEvents { + return [BLEModuleImpl supportedEventNames]; +} + +// MARK: - Lifecycle + +RCT_EXPORT_METHOD(createClient:(NSString *)restoreStateIdentifier + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl createClientWithRestoreStateIdentifier:restoreStateIdentifier resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(destroyClient:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl destroyClientWithResolve:resolve reject:reject]; +} + +// MARK: - State + +RCT_EXPORT_METHOD(state:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl stateWithResolve:resolve reject:reject]; +} + +// MARK: - Scanning + +RCT_EXPORT_METHOD(startDeviceScan:(NSArray *)uuids + options:(NSDictionary *)options) { + [_impl startDeviceScanWithUuids:uuids options:options]; +} + +RCT_EXPORT_METHOD(stopDeviceScan:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl stopDeviceScanWithResolve:resolve reject:reject]; +} + +// MARK: - Connection + +RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId + options:(NSDictionary *)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl connectToDeviceWithDeviceId:deviceId options:options resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(cancelDeviceConnection:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl cancelDeviceConnectionWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(isDeviceConnected:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl isDeviceConnectedWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +// MARK: - Discovery + +RCT_EXPORT_METHOD(discoverAllServicesAndCharacteristics:(NSString *)deviceId + transactionId:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl discoverAllServicesAndCharacteristicsWithDeviceId:deviceId transactionId:transactionId resolve:resolve reject:reject]; +} + +// MARK: - Read/Write + +RCT_EXPORT_METHOD(readCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + transactionId:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl readCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid transactionId:transactionId resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(writeCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + value:(NSString *)value + withResponse:(BOOL)withResponse + transactionId:(NSString *)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 + +RCT_EXPORT_METHOD(monitorCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + subscriptionType:(NSString *)subscriptionType + transactionId:(NSString *)transactionId) { + [_impl monitorCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid subscriptionType:subscriptionType transactionId:transactionId]; +} + +// MARK: - MTU + +RCT_EXPORT_METHOD(getMtu:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl getMtuWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(requestMtu:(NSString *)deviceId + mtu:(double)mtu + transactionId:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl requestMtuWithDeviceId:deviceId mtu:(NSInteger)mtu transactionId:transactionId resolve:resolve reject:reject]; +} + +// MARK: - PHY + +RCT_EXPORT_METHOD(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]; +} + +RCT_EXPORT_METHOD(readPhy:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl readPhyWithDeviceId:deviceId resolve:resolve reject:reject]; +} + +// MARK: - Connection Priority + +RCT_EXPORT_METHOD(requestConnectionPriority:(NSString *)deviceId + priority:(double)priority + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl requestConnectionPriorityWithDeviceId:deviceId priority:(NSInteger)priority resolve:resolve reject:reject]; +} + +// MARK: - L2CAP + +RCT_EXPORT_METHOD(openL2CAPChannel:(NSString *)deviceId + psm:(double)psm + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl openL2CAPChannelWithDeviceId:deviceId psm:(NSInteger)psm resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(writeL2CAPChannel:(double)channelId + data:(NSString *)data + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl writeL2CAPChannelWithChannelId:(NSInteger)channelId data:data resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(closeL2CAPChannel:(double)channelId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl closeL2CAPChannelWithChannelId:(NSInteger)channelId resolve:resolve reject:reject]; +} + +// MARK: - Bonding + +RCT_EXPORT_METHOD(getBondedDevices:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl getBondedDevicesWithResolve:resolve reject:reject]; +} + +// MARK: - Authorization + +RCT_EXPORT_METHOD(getAuthorizationStatus:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl getAuthorizationStatusWithResolve:resolve reject:reject]; +} + +// MARK: - Cancellation + +RCT_EXPORT_METHOD(cancelTransaction:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + [_impl cancelTransactionWithTransactionId:transactionId resolve:resolve reject:reject]; +} + +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} +#endif + +@end diff --git a/ios/CentralManagerDelegate.swift b/ios/CentralManagerDelegate.swift new file mode 100644 index 00000000..a769c1d5 --- /dev/null +++ b/ios/CentralManagerDelegate.swift @@ -0,0 +1,195 @@ +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<[String: Any]>() + + /// Pending connection continuations keyed by peripheral UUID + private let pendingConnections = LockedDictionary>() + + // MARK: - Streams + + var stateStream: AsyncStream { stateSubject.stream } + var scanStream: AsyncStream { scanSubject.stream } + var disconnectionStream: AsyncStream { disconnectionSubject.stream } + var restorationStream: AsyncStream<[String: Any]> { 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 + ) { + let snapshot = EventSerializer.scanResult( + from: peripheral, + advertisementData: advertisementData, + rssi: RSSI + ) + scanSubject.yield(snapshot) + } + + 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 + restorationSubject.yield(dict) + } + + @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 } } + } +} + +/// 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 removeAll(handler: (Value) -> Void) { + lock.withLock { + for (_, value) in dict { + handler(value) + } + dict.removeAll() + } + } +} diff --git a/ios/ErrorConverter.swift b/ios/ErrorConverter.swift new file mode 100644 index 00000000..c46a9ca9 --- /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 00000000..38bf3a6d --- /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 00000000..6fe19578 --- /dev/null +++ b/ios/GATTOperationQueue.swift @@ -0,0 +1,110 @@ +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 + } + + 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) + } + + 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 00000000..96d51a7d --- /dev/null +++ b/ios/L2CAPManager.swift @@ -0,0 +1,210 @@ +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") + } + + let bytesWritten = data.withUnsafeBytes { buffer -> Int in + guard let ptr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return -1 } + return outputStream.write(ptr, maxLength: data.count) + } + + if bytesWritten < 0 { + throw BleError( + code: .l2capWriteFailed, + message: "L2CAP write failed: \(outputStream.streamError?.localizedDescription ?? "unknown error")" + ) + } + } + + func close() { + inputStream.close() + outputStream.close() + inputStream.remove(from: .current, forMode: .default) + outputStream.remove(from: .current, forMode: .default) + 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 00000000..1dba787e --- /dev/null +++ b/ios/PeripheralDelegate.swift @@ -0,0 +1,250 @@ +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() + + // 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 + } + + // 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 — 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 00000000..4ebd2937 --- /dev/null +++ b/ios/PeripheralWrapper.swift @@ -0,0 +1,207 @@ +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: DispatchQueue + 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: DispatchQueue) { + 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) { + 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 00000000..dbc42609 --- /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: DispatchQueue + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + init(queue: DispatchQueue) { + 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 00000000..db1f5acd --- /dev/null +++ b/ios/StateRestoration.swift @@ -0,0 +1,74 @@ +import Foundation +@preconcurrency import CoreBluetooth + +// MARK: - RestoredPeripheralInfo + +struct RestoredPeripheralInfo: Sendable { + let id: String + let name: String? +} + +// 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: [[String: Any]] = [] + private var hasJSSubscribed = false + + let queue: DispatchQueue + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + init(queue: DispatchQueue) { + 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 restoration data for when JS subscribes + bufferedRestorationData.append(dict) + + return wrappers + } + + /// Called when JS subscribes to restoration events. + /// Returns any buffered data, then clears the buffer. + func getBufferedRestorationData() -> [[String: Any]] { + 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/react-native-ble-plx.podspec b/react-native-ble-plx.podspec index c33512ec..74854957 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 => "14.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.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", + "SWIFT_OBJC_BRIDGING_HEADER" => "$(PODS_TARGET_SRCROOT)/ios/BlePlx-Bridging-Header.h", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + install_modules_dependencies(s) end \ No newline at end of file From 4fb56cdd5f0f2878a837c5981938d5107e549b21 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:22:39 -0500 Subject: [PATCH 22/36] =?UTF-8?q?fix:=20address=20Codex=20review=20?= =?UTF-8?q?=E2=80=94=20error=20routing,=20teardown=20order,=20subscription?= =?UTF-8?q?=20cleanup,=20missing=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EventBatcher.dispose() now discards buffered events instead of flushing, preventing callbacks from firing during teardown - Use Platform.OS instead of hardcoded 'ios' in onDeviceDisconnected - Track consumer subscriptions (onStateChange, onDeviceDisconnected, etc.) and clean them up in destroyClient() - Clean up existing monitor when duplicate transactionId is provided - Guard onStateChange callback with removed flag to prevent post-remove firing - Clean up previous errorSubscription in createClient() to prevent leaks - Add missing API methods: getMtu, requestPhy, readPhy, getBondedDevices, getAuthorizationStatus, L2CAP channel ops, onRestoreState, onBondStateChange, onConnectionEvent - Add BleManagerOptions with scanBatchIntervalMs constructor option - Add subscriptionType to MonitorOptions - Update tests to cover all new behavior --- __tests__/BleManager.test.ts | 689 +++++++++++++++++++++++++++++++++ __tests__/EventBatcher.test.ts | 27 ++ src/BleManager.ts | 209 +++++++++- src/EventBatcher.ts | 2 +- 4 files changed, 915 insertions(+), 12 deletions(-) create mode 100644 __tests__/BleManager.test.ts diff --git a/__tests__/BleManager.test.ts b/__tests__/BleManager.test.ts new file mode 100644 index 00000000..dd49f6b5 --- /dev/null +++ b/__tests__/BleManager.test.ts @@ -0,0 +1,689 @@ +// __tests__/BleManager.test.ts + +// --------------------------------------------------------------------------- +// Mock setup — must happen 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> = [] +let stateChangeHandler: ((event: any) => void) | null = null +let restoreStateHandler: ((event: any) => void) | null = null +let bondStateChangeHandler: ((event: any) => void) | null = null +let connectionEventHandler: ((event: any) => 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 + }) + } + }) +} + +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: any[] = [] + manager.startDeviceScan(null, null, (_err, device) => { + if (device) received.push(device) + }) + + expect(mockNativeModule.startDeviceScan).toHaveBeenCalledWith(null, 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: any[] = [] + 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: any[] = [] + 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: any[] = [] + 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 any[])[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 any[])[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, any] + 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 any[][] + 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 any[])[4] + const txId2 = (mockNativeModule.monitorCharacteristic.mock.calls[1] as any[])[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 any[] + 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() + }) +}) diff --git a/__tests__/EventBatcher.test.ts b/__tests__/EventBatcher.test.ts index 4900bf9f..652fba2f 100644 --- a/__tests__/EventBatcher.test.ts +++ b/__tests__/EventBatcher.test.ts @@ -40,3 +40,30 @@ test('max batch size caps delivery', 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/src/BleManager.ts b/src/BleManager.ts index b02f7102..aa6bd96a 100644 --- a/src/BleManager.ts +++ b/src/BleManager.ts @@ -1,4 +1,4 @@ -import { TurboModuleRegistry } from 'react-native' +import { TurboModuleRegistry, Platform } from 'react-native' import type { EventSubscription, TurboModule } from 'react-native' import type { State, ScanOptions, ConnectOptions, ConnectionPriority } from './types' import { BleError } from './BleError' @@ -75,6 +75,32 @@ interface BleErrorInfo { 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 @@ -126,19 +152,41 @@ interface NativeBlePlxSpec extends TurboModule { 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 } // --------------------------------------------------------------------------- // Public types // --------------------------------------------------------------------------- -export type { DeviceInfo, CharacteristicInfo, ScanResult, ConnectionStateEvent, CharacteristicValueEvent } +export type { + DeviceInfo, + CharacteristicInfo, + ScanResult, + ConnectionStateEvent, + CharacteristicValueEvent, + BondStateEvent, + ConnectionEvent, + RestoreStateEvent, + L2CAPChannelEvent, + PhyInfo +} export interface Subscription { remove(): void @@ -147,6 +195,11 @@ export interface Subscription { export interface MonitorOptions { transactionId?: string batchInterval?: number + subscriptionType?: 'notify' | 'indicate' | null +} + +export interface BleManagerOptions { + scanBatchIntervalMs?: number } // --------------------------------------------------------------------------- @@ -181,9 +234,12 @@ export class BleManager { private monitorBatchers = new Map>() private monitorSubscriptions = new Map() private activeTransactionIds = new Set() + private consumerSubscriptions = new Set() + private scanBatchIntervalMs: number - constructor() { + constructor(options?: BleManagerOptions) { this.nativeModule = getNativeModule() + this.scanBatchIntervalMs = options?.scanBatchIntervalMs ?? 100 } // ----------------------------------------------------------------------- @@ -191,6 +247,10 @@ export class BleManager { // ----------------------------------------------------------------------- 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 @@ -214,6 +274,10 @@ export class BleManager { 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(() => { @@ -240,24 +304,36 @@ export class BleManager { } onStateChange(callback: (state: State) => void, emitCurrentState?: boolean): Subscription { + let removed = false + const sub = this.nativeModule.onStateChange(event => { - callback(event.state as State) + if (!removed) { + callback(event.state as State) + } }) if (emitCurrentState) { this.nativeModule.state().then( - s => callback(s as State), + s => { + if (!removed) callback(s as State) + }, () => { // Ignore errors when reading initial state } ) } - return { + const subscription: Subscription = { remove: () => { + if (removed) return + removed = true sub.remove() + this.consumerSubscriptions.delete(subscription) } } + + this.consumerSubscriptions.add(subscription) + return subscription } // ----------------------------------------------------------------------- @@ -272,8 +348,8 @@ export class BleManager { // Clean up any previous scan this.stopScanInternal() - // Create batcher for scan results (100ms interval, max 50 per batch) - this.scanBatcher = new EventBatcher(100, 50, batch => { + // Create batcher for scan results + this.scanBatcher = new EventBatcher(this.scanBatchIntervalMs, 50, batch => { batch.forEach(result => callback(null, result)) }) @@ -327,7 +403,7 @@ export class BleManager { code: event.errorCode, message: event.errorMessage, isRetryable: false, - platform: 'ios', + platform: Platform.OS === 'ios' ? 'ios' : 'android', deviceId: event.deviceId }) callback(bleError, event) @@ -336,11 +412,15 @@ export class BleManager { } }) - return { + const subscription: Subscription = { remove: () => { sub.remove() + this.consumerSubscriptions.delete(subscription) } } + + this.consumerSubscriptions.add(subscription) + return subscription } // ----------------------------------------------------------------------- @@ -395,6 +475,19 @@ export class BleManager { ): 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) @@ -416,7 +509,7 @@ export class BleManager { this.monitorSubscriptions.set(txId, sub) // Tell native to start monitoring - this.nativeModule.monitorCharacteristic(deviceId, serviceUuid, characteristicUuid, null, txId) + this.nativeModule.monitorCharacteristic(deviceId, serviceUuid, characteristicUuid, subscriptionType, txId) let removed = false return { @@ -449,6 +542,54 @@ export class BleManager { 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) + } + // ----------------------------------------------------------------------- // Connection Priority // ----------------------------------------------------------------------- @@ -457,6 +598,52 @@ export class BleManager { 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 // ----------------------------------------------------------------------- diff --git a/src/EventBatcher.ts b/src/EventBatcher.ts index 31a0107e..388d65d2 100644 --- a/src/EventBatcher.ts +++ b/src/EventBatcher.ts @@ -37,6 +37,6 @@ export class EventBatcher { clearInterval(this.timer) this.timer = null } - this.flush() + this.buffer.length = 0 } } From 22bdda718ababa88c4d8c673a46adf325efa2250 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:33:31 -0500 Subject: [PATCH 23/36] test: add JS protocol tests, integration flow tests, and hardware test scaffolding - __tests__/protocol.test.ts: 32 tests covering base64 round-trip, DeviceInfo and CharacteristicInfo shape parsing, BleError construction from native error events, BleErrorCode distinctness, and State/ConnectionState/ConnectionPriority const object values - integration-tests/simulated/js/fullSync.test.ts: full JS-layer flow tests (mocked TurboModule) covering createClient->scan->connect->write->monitor-> disconnect, audit #3 subscription.remove() fix verification, and error scenarios (permission denied, timeout, GATT error) - integration-tests/hardware/maestro/: three Maestro flow scaffolds (scan-pair-sync, disconnect-recovery, indicate-stress) with inline comments - integration-tests/hardware/peripheral-firmware/README.md: GATT profile requirements for the BlePlxTest peripheral - integration-tests/hardware/test-app/README.md: test app setup and testID conventions - android/src/test/kotlin/com/bleplx/README.md: JUnit 5 + MockK test plan - ios/Tests/README.md: XCTest + CoreBluetoothMock test plan --- __tests__/protocol.test.ts | 467 +++++++++++++ android/src/test/kotlin/com/bleplx/README.md | 86 +++ .../hardware/maestro/disconnect-recovery.yaml | 109 ++++ .../hardware/maestro/indicate-stress.yaml | 144 ++++ .../hardware/maestro/scan-pair-sync.yaml | 97 +++ .../hardware/peripheral-firmware/README.md | 56 ++ integration-tests/hardware/test-app/README.md | 70 ++ .../simulated/js/fullSync.test.ts | 615 ++++++++++++++++++ ios/Tests/README.md | 120 ++++ 9 files changed, 1764 insertions(+) create mode 100644 __tests__/protocol.test.ts create mode 100644 android/src/test/kotlin/com/bleplx/README.md create mode 100644 integration-tests/hardware/maestro/disconnect-recovery.yaml create mode 100644 integration-tests/hardware/maestro/indicate-stress.yaml create mode 100644 integration-tests/hardware/maestro/scan-pair-sync.yaml create mode 100644 integration-tests/hardware/peripheral-firmware/README.md create mode 100644 integration-tests/hardware/test-app/README.md create mode 100644 integration-tests/simulated/js/fullSync.test.ts create mode 100644 ios/Tests/README.md diff --git a/__tests__/protocol.test.ts b/__tests__/protocol.test.ts new file mode 100644 index 00000000..88e3c0a6 --- /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/src/test/kotlin/com/bleplx/README.md b/android/src/test/kotlin/com/bleplx/README.md new file mode 100644 index 00000000..ee282ea8 --- /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/integration-tests/hardware/maestro/disconnect-recovery.yaml b/integration-tests/hardware/maestro/disconnect-recovery.yaml new file mode 100644 index 00000000..adf49031 --- /dev/null +++ b/integration-tests/hardware/maestro/disconnect-recovery.yaml @@ -0,0 +1,109 @@ +# Maestro flow: Disconnect Recovery +# +# This flow verifies that the library correctly handles an unexpected +# disconnection (e.g., the peripheral goes out of range or is powered off) +# and that the app can re-connect successfully after recovery. +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on initially. +# - The tester (or an automated relay) powers off the peripheral during +# the test to simulate an unexpected disconnection. +# - The test app is installed and foregrounded. +# +# Note: The "power off peripheral" step below is annotated as a manual action. +# When running on a device farm with controllable hardware, replace the +# instructional comment with the relevant relay/GPIO command. +# +# Run: +# maestro test integration-tests/hardware/maestro/disconnect-recovery.yaml + +appId: com.bleplxexample + +--- + +# --------------------------------------------------------------------------- +# 1. Launch and connect to the peripheral +# --------------------------------------------------------------------------- +- launchApp: + clearState: true + +- assertVisible: "Start Scan" + +- tapOn: + id: "start-scan-button" + +- assertVisible: + text: "BlePlxTest" + timeout: 15000 + +- tapOn: + text: "BlePlxTest" + +- assertVisible: + id: "connection-state-label" + text: "connected" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 2. Verify steady-state: receiving notifications +# --------------------------------------------------------------------------- +- assertVisible: + id: "char-value" + timeout: 5000 + +# --------------------------------------------------------------------------- +# 3. Simulate unexpected disconnection +# +# MANUAL STEP: power off the BlePlxTest peripheral now. +# When using a BLE relay board, insert the relay-off command here, e.g.: +# - evalScript: ${relay.off('BlePlxTest')} +# --------------------------------------------------------------------------- + +# The library should detect the link loss and fire onDeviceDisconnected. +# The app displays the disconnected state. +- assertVisible: + id: "connection-state-label" + text: "disconnected" + timeout: 15000 + +# The app should also show an error label since this was unexpected +- assertVisible: + id: "error-label" + timeout: 3000 + +# --------------------------------------------------------------------------- +# 4. Recovery: power on the peripheral and reconnect +# +# MANUAL STEP: power on the BlePlxTest peripheral now. +# --------------------------------------------------------------------------- + +- tapOn: + id: "start-scan-button" + +- assertVisible: + text: "BlePlxTest" + timeout: 20000 + +- tapOn: + text: "BlePlxTest" + +- assertVisible: + id: "connection-state-label" + text: "connected" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 5. Verify notifications resume after re-connect +# --------------------------------------------------------------------------- +- assertVisible: + id: "char-value" + timeout: 5000 + +# Clean disconnect +- tapOn: + id: "disconnect-button" + +- assertVisible: + id: "connection-state-label" + text: "disconnected" + timeout: 5000 diff --git a/integration-tests/hardware/maestro/indicate-stress.yaml b/integration-tests/hardware/maestro/indicate-stress.yaml new file mode 100644 index 00000000..3f77acc8 --- /dev/null +++ b/integration-tests/hardware/maestro/indicate-stress.yaml @@ -0,0 +1,144 @@ +# Maestro flow: Indicate Stress Test +# +# This flow verifies that the library correctly handles a high-frequency +# stream of BLE indications over an extended period without dropping events, +# leaking memory, or crashing. +# +# The BlePlxTest peripheral sends an indication on characteristic fff2 every +# 250 ms. This flow monitors the stream for 30 seconds and verifies that: +# - At least N indication values are received (N = 30s / 250ms * 0.8 = 96, +# allowing 20% slack for BLE scheduling jitter). +# - No error label appears during the test. +# - The app does not crash or freeze (Maestro can still interact with it). +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on and advertising. +# - The test app is installed and foregrounded. +# - The peripheral firmware must implement the fff2 indicate characteristic +# sending a value every 250 ms when indications are enabled. +# +# Run: +# maestro test integration-tests/hardware/maestro/indicate-stress.yaml + +appId: com.bleplxexample + +--- + +# --------------------------------------------------------------------------- +# 1. Launch and connect +# --------------------------------------------------------------------------- +- launchApp: + clearState: true + +- assertVisible: "Start Scan" + +- tapOn: + id: "start-scan-button" + +- assertVisible: + text: "BlePlxTest" + timeout: 15000 + +- tapOn: + text: "BlePlxTest" + +- assertVisible: + id: "connection-state-label" + text: "connected" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 2. Navigate to the indicate stress-test screen +# +# The test app must expose a button or navigation item to switch to the +# stress-test screen that monitors characteristic fff2 specifically. +# --------------------------------------------------------------------------- +- tapOn: + id: "stress-test-button" + +- assertVisible: + id: "indicate-count-label" + timeout: 3000 + +# --------------------------------------------------------------------------- +# 3. Run for 30 seconds +# +# Maestro does not have a native timer loop, so we use a sequence of +# waitForAnimationToEnd calls as a coarse delay. Adjust the number of +# iterations based on actual animation timing in your app. +# +# Each assertVisible re-checks that the count is still updating and no +# error has appeared. +# --------------------------------------------------------------------------- + +# Check at t ≈ 5 s +- waitForAnimationToEnd: + timeout: 5000 +- assertNotVisible: + id: "error-label" +- assertVisible: + id: "indicate-count-label" + +# Check at t ≈ 10 s +- waitForAnimationToEnd: + timeout: 5000 +- assertNotVisible: + id: "error-label" +- assertVisible: + id: "indicate-count-label" + +# Check at t ≈ 15 s +- waitForAnimationToEnd: + timeout: 5000 +- assertNotVisible: + id: "error-label" +- assertVisible: + id: "indicate-count-label" + +# Check at t ≈ 20 s +- waitForAnimationToEnd: + timeout: 5000 +- assertNotVisible: + id: "error-label" +- assertVisible: + id: "indicate-count-label" + +# Check at t ≈ 25 s +- waitForAnimationToEnd: + timeout: 5000 +- assertNotVisible: + id: "error-label" +- assertVisible: + id: "indicate-count-label" + +# Final check at t ≈ 30 s +- waitForAnimationToEnd: + timeout: 5000 +- assertNotVisible: + id: "error-label" + +# Verify the counter reached a reasonable minimum (96+ indications). +# The app should display the count as a number in indicate-count-label. +# Maestro cannot do numeric comparisons, so we check for the presence of +# a 2+ digit number by asserting a non-zero label is still visible. +- assertVisible: + id: "indicate-count-label" + +# --------------------------------------------------------------------------- +# 4. Stop monitoring and disconnect cleanly +# --------------------------------------------------------------------------- +- tapOn: + id: "stop-monitor-button" + +# No error should appear after stopping +- assertNotVisible: + id: "error-label" + timeout: 2000 + +- tapOn: + id: "disconnect-button" + +- assertVisible: + id: "connection-state-label" + text: "disconnected" + timeout: 5000 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 00000000..6143553c --- /dev/null +++ b/integration-tests/hardware/maestro/scan-pair-sync.yaml @@ -0,0 +1,97 @@ +# Maestro flow: Scan → Pair → Sync +# +# This flow verifies the complete happy-path of discovering a BLE peripheral, +# connecting to it, performing a write, and receiving a characteristic +# notification. +# +# Prerequisites: +# - The BlePlxTest peripheral is powered on and advertising within range. +# - The test app is installed and foregrounded on the device under test. +# - See integration-tests/hardware/peripheral-firmware/README.md for firmware +# requirements. +# - See integration-tests/hardware/test-app/README.md for app setup. +# +# 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 to be visible +- assertVisible: "Start Scan" + +# --------------------------------------------------------------------------- +# 2. Start scanning for BLE devices +# --------------------------------------------------------------------------- +- tapOn: + id: "start-scan-button" + +# The scan should start — wait for our peripheral to appear in the list. +# The peripheral advertises as "BlePlxTest". +- waitForAnimationToEnd + +- assertVisible: + text: "BlePlxTest" + timeout: 15000 # Allow up to 15 s for the peripheral to appear + +# --------------------------------------------------------------------------- +# 3. Connect to the peripheral +# --------------------------------------------------------------------------- +- tapOn: + text: "BlePlxTest" + +# Wait for the connection state label to show "connected" +- assertVisible: + id: "connection-state-label" + text: "connected" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 4. Discover services and verify the heart rate service UUID is shown +# --------------------------------------------------------------------------- +# The app should automatically discover services after connecting. +# Verify the expected service UUID is displayed. +- assertVisible: + text: "180D" + timeout: 5000 + +# --------------------------------------------------------------------------- +# 5. Write to the writable test characteristic (fff1) +# --------------------------------------------------------------------------- +- tapOn: + id: "write-button" + +# A successful write should not show an error +- assertNotVisible: + id: "error-label" + timeout: 3000 + +# --------------------------------------------------------------------------- +# 6. Verify a notification is received from the heart rate characteristic +# --------------------------------------------------------------------------- +# The peripheral sends HR notifications at 1 Hz. +# Wait for the characteristic value label to be non-empty. +- assertVisible: + id: "char-value" + timeout: 5000 + +# The value should be a valid base64-encoded HR measurement (non-empty) +# Maestro does not support regex assertions, so we just check visibility. + +# --------------------------------------------------------------------------- +# 7. Disconnect +# --------------------------------------------------------------------------- +- tapOn: + id: "disconnect-button" + +- assertVisible: + id: "connection-state-label" + text: "disconnected" + timeout: 5000 diff --git a/integration-tests/hardware/peripheral-firmware/README.md b/integration-tests/hardware/peripheral-firmware/README.md new file mode 100644 index 00000000..6ea2f513 --- /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/test-app/README.md b/integration-tests/hardware/test-app/README.md new file mode 100644 index 00000000..3cd95007 --- /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 00000000..4b46de2e --- /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/Tests/README.md b/ios/Tests/README.md new file mode 100644 index 00000000..5ff60b74 --- /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. From be3e97af67df717c9055dc3e0b95a31fb3f25d9d Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:38:52 -0500 Subject: [PATCH 24/36] =?UTF-8?q?fix:=20address=20Codex=20native=20review?= =?UTF-8?q?=20=E2=80=94=20GATT=20cancel,=20L2CAP=20runloop,=20error=20code?= =?UTF-8?q?s,=20Swift=20sendable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/bleplx/BleManagerWrapper.kt | 6 +++ .../main/kotlin/com/bleplx/ErrorConverter.kt | 27 +++++++++++ ios/BLEActor.swift | 18 +++++--- ios/CentralManagerDelegate.swift | 24 ++++++++-- ios/GATTOperationQueue.swift | 13 ++++++ ios/L2CAPManager.swift | 14 ++++-- ios/StateRestoration.swift | 45 +++++++++++++++++-- 7 files changed, 128 insertions(+), 19 deletions(-) diff --git a/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt index 64f8c07c..b33625e1 100644 --- a/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt +++ b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt @@ -25,6 +25,12 @@ import kotlin.coroutines.resumeWithException * * 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) { diff --git a/android/src/main/kotlin/com/bleplx/ErrorConverter.kt b/android/src/main/kotlin/com/bleplx/ErrorConverter.kt index 5fad20e1..638dd24d 100644 --- a/android/src/main/kotlin/com/bleplx/ErrorConverter.kt +++ b/android/src/main/kotlin/com/bleplx/ErrorConverter.kt @@ -107,6 +107,33 @@ object ErrorConverter { 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, diff --git a/ios/BLEActor.swift b/ios/BLEActor.swift index f0b1d51e..4427f627 100644 --- a/ios/BLEActor.swift +++ b/ios/BLEActor.swift @@ -103,14 +103,18 @@ actor BLEActor { // Monitor restoration restorationTask = Task { [weak self, delegateHandler] in - for await dict in delegateHandler.restorationStream { + for await _ in delegateHandler.restorationStream { guard let self = self else { break } - 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) + // 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) diff --git a/ios/CentralManagerDelegate.swift b/ios/CentralManagerDelegate.swift index a769c1d5..f996680d 100644 --- a/ios/CentralManagerDelegate.swift +++ b/ios/CentralManagerDelegate.swift @@ -20,17 +20,21 @@ final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate, Sendable private let scanSubject = AsyncStreamBridge() private let connectionSubject = AsyncStreamBridge() private let disconnectionSubject = AsyncStreamBridge() - private let restorationSubject = AsyncStreamBridge<[String: Any]>() + 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]]>([]) + // MARK: - Streams var stateStream: AsyncStream { stateSubject.stream } var scanStream: AsyncStream { scanSubject.stream } var disconnectionStream: AsyncStream { disconnectionSubject.stream } - var restorationStream: AsyncStream<[String: Any]> { restorationSubject.stream } + var restorationStream: AsyncStream { restorationSubject.stream } // MARK: - Connection management @@ -98,8 +102,20 @@ final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate, Sendable } func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) { - // willRestoreState fires BEFORE didUpdateState - restorationSubject.yield(dict) + // 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, *) diff --git a/ios/GATTOperationQueue.swift b/ios/GATTOperationQueue.swift index 6fe19578..74b0d540 100644 --- a/ios/GATTOperationQueue.swift +++ b/ios/GATTOperationQueue.swift @@ -51,6 +51,14 @@ actor GATTOperationQueue { 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 { @@ -93,6 +101,11 @@ actor GATTOperationQueue { 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 diff --git a/ios/L2CAPManager.swift b/ios/L2CAPManager.swift index 96d51a7d..833fbb96 100644 --- a/ios/L2CAPManager.swift +++ b/ios/L2CAPManager.swift @@ -66,10 +66,16 @@ final class L2CAPChannelWrapper: NSObject, StreamDelegate, @unchecked Sendable { } func close() { - inputStream.close() - outputStream.close() - inputStream.remove(from: .current, forMode: .default) - outputStream.remove(from: .current, forMode: .default) + // 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() } diff --git a/ios/StateRestoration.swift b/ios/StateRestoration.swift index db1f5acd..8f3d0496 100644 --- a/ios/StateRestoration.swift +++ b/ios/StateRestoration.swift @@ -8,6 +8,43 @@ struct RestoredPeripheralInfo: Sendable { 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. @@ -16,7 +53,7 @@ struct RestoredPeripheralInfo: Sendable { /// - Buffers restoration data until JS subscribes actor StateRestoration { private var restoredPeripherals: [UUID: CBPeripheral] = [:] - private var bufferedRestorationData: [[String: Any]] = [] + private var bufferedRestorationData: [RestorationState] = [] private var hasJSSubscribed = false let queue: DispatchQueue @@ -44,15 +81,15 @@ actor StateRestoration { } } - // Buffer the restoration data for when JS subscribes - bufferedRestorationData.append(dict) + // 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() -> [[String: Any]] { + func getBufferedRestorationData() -> [RestorationState] { hasJSSubscribed = true let data = bufferedRestorationData bufferedRestorationData.removeAll() From 79bf95dfba6952156874f7e52bb68553b9675e06 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 14:39:53 -0500 Subject: [PATCH 25/36] =?UTF-8?q?fix(ios):=20handle=20L2CAP=20partial=20wr?= =?UTF-8?q?ites=20=E2=80=94=20loop=20until=20all=20bytes=20sent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/L2CAPManager.swift | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/ios/L2CAPManager.swift b/ios/L2CAPManager.swift index 833fbb96..5bf74577 100644 --- a/ios/L2CAPManager.swift +++ b/ios/L2CAPManager.swift @@ -52,16 +52,29 @@ final class L2CAPChannelWrapper: NSObject, StreamDelegate, @unchecked Sendable { throw BleError(code: .l2capWriteFailed, message: "L2CAP output stream not ready") } - let bytesWritten = data.withUnsafeBytes { buffer -> Int in - guard let ptr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return -1 } - return outputStream.write(ptr, maxLength: data.count) - } + // 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") + } - if bytesWritten < 0 { - throw BleError( - code: .l2capWriteFailed, - message: "L2CAP write failed: \(outputStream.streamError?.localizedDescription ?? "unknown error")" - ) + offset += bytesWritten } } From 947ee8225b18d8f885f9a692246a5edd1536bf62 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 15:08:59 -0500 Subject: [PATCH 26/36] chore: remove all v3 JS/Flow source, tests, and config - Delete 13 v3 JS source files (replaced by TypeScript in v4) - Delete 9 v3 JS test files (replaced by v4 .test.ts files) - Delete .flowconfig - Remove Flow/hermes devDependencies from package.json - Remove Flow/hermes ESLint override from .eslintrc.json - Clean up .eslintignore (no more v3 JS files to ignore) --- .eslintignore | 3 - .eslintrc.json | 32 +- .flowconfig | 84 - __tests__/BleManager.js | 412 ---- __tests__/Characteristic.js | 47 - __tests__/Descriptor.js | 27 - __tests__/Device.js | 116 - __tests__/Service.js | 60 - __tests__/Utils.js | 72 - ...3-15-plan-e-cleanup-testing-integration.md | 314 +++ package.json | 10 +- plugin/tsconfig.tsbuildinfo | 1 + src/BleError.js | 555 ----- src/BleManager.js | 1320 ----------- src/BleModule.js | 857 ------- src/Characteristic.js | 180 -- src/Descriptor.js | 83 - src/Device.js | 379 --- src/Service.js | 203 -- src/TypeDefinition.js | 314 --- src/Utils.js | 29 - src/index.d.ts | 2084 ----------------- src/index.js | 20 - tsconfig.tests.json | 11 + 24 files changed, 330 insertions(+), 6883 deletions(-) delete mode 100644 .flowconfig delete mode 100644 __tests__/BleManager.js delete mode 100644 __tests__/Characteristic.js delete mode 100644 __tests__/Descriptor.js delete mode 100644 __tests__/Device.js delete mode 100644 __tests__/Service.js delete mode 100644 __tests__/Utils.js create mode 100644 docs/plans/2026-03-15-plan-e-cleanup-testing-integration.md create mode 100644 plugin/tsconfig.tsbuildinfo delete mode 100644 src/BleError.js delete mode 100644 src/BleManager.js delete mode 100644 src/BleModule.js delete mode 100644 src/Characteristic.js delete mode 100644 src/Descriptor.js delete mode 100644 src/Device.js delete mode 100644 src/Service.js delete mode 100644 src/TypeDefinition.js delete mode 100644 src/Utils.js delete mode 100644 src/index.d.ts delete mode 100644 src/index.js create mode 100644 tsconfig.tests.json diff --git a/.eslintignore b/.eslintignore index b9385f45..68b1c204 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,3 @@ node_modules/ docs/** plugin/build lib/** -src/index.d.ts -src/*.js -__tests__/*.js diff --git a/.eslintrc.json b/.eslintrc.json index 8279a262..a8c0c632 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,35 +7,6 @@ "es2020": true }, "overrides": [ - { - "files": ["src/**/*.js", "src/**/*.jsx", "__tests__/**/*.js", "__tests__/**/*.jsx", "integration-tests/**/*.js", "integration-tests/**/*.jsx"], - "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": ["src/**/*.tsx", "src/**/*.ts", "src/**/*.d.ts", "__tests__/**/*.ts", "__tests__/**/*.tsx", "plugin/**/*.ts", "plugin/**/*.tsx"], "parser": "@typescript-eslint/parser", @@ -104,6 +75,9 @@ }, { "files": ["__tests__/**/*.ts", "__tests__/**/*.tsx"], + "parserOptions": { + "project": ["./tsconfig.tests.json"] + }, "rules": { "@typescript-eslint/no-non-null-assertion": "off" } diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index ea5761b1..00000000 --- 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/__tests__/BleManager.js b/__tests__/BleManager.js deleted file mode 100644 index 07453017..00000000 --- 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__/Characteristic.js b/__tests__/Characteristic.js deleted file mode 100644 index b74c4ecb..00000000 --- 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 5c6fd24b..00000000 --- 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 67d4c595..00000000 --- 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__/Service.js b/__tests__/Service.js deleted file mode 100644 index 6f1b914d..00000000 --- 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 71994a14..00000000 --- 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/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 00000000..38067d5c --- /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/package.json b/package.json index 13311ebe..c287d709 100644 --- a/package.json +++ b/package.json @@ -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": "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", @@ -90,23 +88,17 @@ "@types/react": "^18.2.44", "@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-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", diff --git a/plugin/tsconfig.tsbuildinfo b/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..5802b7d1 --- /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/src/BleError.js b/src/BleError.js deleted file mode 100644 index 73b973c4..00000000 --- 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/BleManager.js b/src/BleManager.js deleted file mode 100644 index 4e388873..00000000 --- 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/BleModule.js b/src/BleModule.js deleted file mode 100644 index c9c5ea03..00000000 --- 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 492cbb97..00000000 --- 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/Descriptor.js b/src/Descriptor.js deleted file mode 100644 index d8d47622..00000000 --- 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/Device.js b/src/Device.js deleted file mode 100644 index 59c72bed..00000000 --- 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/Service.js b/src/Service.js deleted file mode 100644 index 29ba5a8d..00000000 --- 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/TypeDefinition.js b/src/TypeDefinition.js deleted file mode 100644 index fb954bd0..00000000 --- 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 a7e1560e..00000000 --- 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 d1ef75c9..00000000 --- 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 4d720589..00000000 --- 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/tsconfig.tests.json b/tsconfig.tests.json new file mode 100644 index 00000000..08bef073 --- /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__/**/*"] +} From 9d9bfd064321f04aec7a77bf67b5cd2e51841087 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 15:12:51 -0500 Subject: [PATCH 27/36] feat: nRF52840 BLE test peripheral firmware for integration testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Arduino sketch for Seeed XIAO nRF52840 that exposes a five-characteristic GATT test service covering read, authenticated write, notify, indicate, and MTU negotiation — the core BLE operations exercised by react-native-ble-plx v4. --- .../ble_test_peripheral.ino | 507 ++++++++++++++++++ .../ble_test_peripheral/config.h | 67 +++ 2 files changed, 574 insertions(+) create mode 100644 integration-tests/hardware/peripheral-firmware/ble_test_peripheral/ble_test_peripheral.ino create mode 100644 integration-tests/hardware/peripheral-firmware/ble_test_peripheral/config.h 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 00000000..aa28958d --- /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 00000000..4bfda85f --- /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 From 58bccc0649066524f6cd1469bc7c3c84b2373976 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 15:38:26 -0500 Subject: [PATCH 28/36] feat(example): wire v4 library, add test screens, fix both platform builds - Wire react-native-ble-plx (file:..) into example app with navigation deps - Create ScanScreen, DeviceScreen, CharacteristicScreen test screens - Update Metro config to resolve library from parent directory - Fix Android: BleManagerWrapper notification callback (DataReceivedCallback via setNotificationCallback, not enableIndications.with()) - Fix iOS: migrate from bridging header to module imports for framework target - Fix iOS: replace DispatchQueue.asUnownedSerialExecutor() with DispatchSerialQueue (required for Swift 6.2 / Xcode 26) - Fix iOS: refactor BLEModuleImpl to use typed CodeGen emit methods instead of RCTEventEmitter.sendEvent pattern - Bump iOS deployment target to 17.0 (required for DispatchSerialQueue) - Downgrade Gradle to 8.13 for JDK 21 compatibility - Both Android assembleDebug and iOS xcodebuild succeed --- .../kotlin/com/bleplx/BleManagerWrapper.kt | 5 +- example/.eslintrc.js | 4 + example/.gitignore | 75 + example/.prettierrc.js | 5 + example/App.tsx | 1 + example/Gemfile | 8 +- example/Gemfile.lock | 118 - example/README.md | 64 +- example/__tests__/App.test.tsx | 13 + example/android/app/build.gradle | 8 +- .../android/app/src/debug/AndroidManifest.xml | 13 - .../android/app/src/main/AndroidManifest.xml | 10 +- .../java/com/bleplxexample/MainApplication.kt | 41 +- .../app/src/main/res/values/styles.xml | 3 +- example/android/build.gradle | 16 +- example/android/gradle.properties | 9 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 45457 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/gradlew | 11 +- example/android/settings.gradle | 2 +- example/app.json | 10 +- example/babel.config.js | 16 +- .../docs/nRFDeviceTesting/BLE-PLX-example.xml | 21 - .../docs/nRFDeviceTesting/nRFDeviceTesting.md | 36 - example/index.js | 12 +- example/ios/AppDelegate.swift | 30 - example/ios/BlePlxExample-Bridging-Header.h | 3 - .../BlePlxExample.xcodeproj/project.pbxproj | 199 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 - example/ios/BlePlxExample/AppDelegate.swift | 48 + .../AppIcon.appiconset/AppIcon-20@2x.png | Bin 1253 -> 0 bytes .../AppIcon.appiconset/AppIcon-20@3x.png | Bin 2363 -> 0 bytes .../AppIcon.appiconset/AppIcon-29@2x~ipad.png | Bin 2176 -> 0 bytes .../AppIcon.appiconset/AppIcon-29@3x.png | Bin 3057 -> 0 bytes .../AppIcon.appiconset/AppIcon-40@2x~ipad.png | Bin 2762 -> 0 bytes .../AppIcon.appiconset/AppIcon-40@3x.png | Bin 5036 -> 0 bytes .../AppIcon.appiconset/AppIcon-60@2x~car.png | Bin 5036 -> 0 bytes .../AppIcon.appiconset/AppIcon-60@3x~car.png | Bin 7056 -> 0 bytes .../AppIcon~ios-marketing.png | Bin 87833 -> 0 bytes .../AppIcon.appiconset/Contents.json | 9 - example/ios/BlePlxExample/Info.plist | 11 +- example/ios/File.swift | 6 - example/ios/Podfile | 3 +- example/ios/Podfile.lock | 1718 ++- example/jest.config.js | 4 +- example/libs/styled.d.ts | 7 - example/metro.config.js | 41 +- example/package-lock.json | 12256 ++++++++++++++++ example/package.json | 50 +- example/react-native.config.js | 10 - example/src/App.tsx | 77 +- .../atoms/AppText/AppText.styled.tsx | 9 - .../src/components/atoms/AppText/AppText.tsx | 7 - .../AppTextInput/AppTextInput.styled.tsx | 15 - .../atoms/AppTextInput/AppTextInput.tsx | 7 - .../components/atoms/Button/Button.styled.tsx | 17 - .../src/components/atoms/Button/Button.tsx | 15 - .../ScreenDefaultContainer.styled.tsx | 9 - .../ScreenDefaultContainer.tsx | 10 - .../TestStateDisplay.styled.tsx | 22 - .../TestStateDisplay/TestStateDisplay.tsx | 29 - example/src/components/atoms/index.ts | 5 - .../molecules/BleDevice/BleDevice.styled.tsx | 12 - .../molecules/BleDevice/BleDevice.tsx | 28 - .../DeviceProperty/DeviceProperty.styled.tsx | 16 - .../DeviceProperty/DeviceProperty.tsx | 16 - example/src/components/molecules/index.ts | 1 - example/src/consts/nRFDeviceConsts.ts | 13 - .../components/commonScreenOptions.tsx | 21 - example/src/navigation/components/index.ts | 1 - example/src/navigation/index.ts | 1 - example/src/navigation/navigation.tsx | 31 - .../src/navigation/navigators/MainStack.tsx | 67 - example/src/navigation/navigators/index.ts | 1 - example/src/screens/CharacteristicScreen.tsx | 195 + example/src/screens/DeviceScreen.tsx | 157 + .../DashboardScreen.styled.tsx | 18 - .../DashboardScreen/DashboardScreen.tsx | 107 - .../DeviceConnectDisconnectTestScreen.tsx | 283 - .../DeviceDetailsScreen.tsx | 19 - .../DeviceOnDisconnectTestScreen.tsx | 134 - .../DevicenRFTestScreen.tsx | 709 - .../InstanceDestroyScreen.tsx | 146 - .../MainStack/InstanceDestroyScreen/utils.ts | 131 - example/src/screens/MainStack/index.ts | 6 - example/src/screens/ScanScreen.tsx | 161 + example/src/screens/index.ts | 1 - example/src/services/BLEService/BLEService.ts | 462 - example/src/services/index.ts | 2 - .../services/storage/persistentDeviceName.ts | 30 - example/src/theme/colors.ts | 3 - example/src/theme/sizes.ts | 4 - example/src/theme/theme.ts | 10 - example/src/types/TestStateType.ts | 1 - example/src/types/index.ts | 1 - example/src/utils/cloneDeep.ts | 1 - example/src/utils/getCurrentTimeAsBase64.ts | 15 - example/src/utils/getDateAsBase64.ts | 14 - example/src/utils/getDateUint8Array.ts | 13 - example/src/utils/isAndroidAbove14.ts | 3 - example/src/utils/wait.ts | 1 - example/tsconfig.json | 8 + example/yarn.lock | 6806 --------- ios/BLEActor.swift | 2 +- ios/BLEModuleImpl.swift | 183 +- ios/BlePlx.mm | 185 +- ios/PeripheralWrapper.swift | 4 +- ios/ScanManager.swift | 4 +- ios/StateRestoration.swift | 4 +- ios/react_native_ble_plx.h | 6 + react-native-ble-plx.podspec | 4 +- 111 files changed, 14351 insertions(+), 10818 deletions(-) create mode 100644 example/.eslintrc.js create mode 100644 example/.gitignore create mode 100644 example/.prettierrc.js create mode 100644 example/App.tsx delete mode 100644 example/Gemfile.lock create mode 100644 example/__tests__/App.test.tsx delete mode 100644 example/android/app/src/debug/AndroidManifest.xml delete mode 100644 example/docs/nRFDeviceTesting/BLE-PLX-example.xml delete mode 100644 example/docs/nRFDeviceTesting/nRFDeviceTesting.md delete mode 100644 example/ios/AppDelegate.swift delete mode 100644 example/ios/BlePlxExample-Bridging-Header.h delete mode 100644 example/ios/BlePlxExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/BlePlxExample/AppDelegate.swift delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png delete mode 100644 example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png delete mode 100644 example/ios/File.swift delete mode 100644 example/libs/styled.d.ts create mode 100644 example/package-lock.json delete mode 100644 example/react-native.config.js delete mode 100644 example/src/components/atoms/AppText/AppText.styled.tsx delete mode 100644 example/src/components/atoms/AppText/AppText.tsx delete mode 100644 example/src/components/atoms/AppTextInput/AppTextInput.styled.tsx delete mode 100644 example/src/components/atoms/AppTextInput/AppTextInput.tsx delete mode 100644 example/src/components/atoms/Button/Button.styled.tsx delete mode 100644 example/src/components/atoms/Button/Button.tsx delete mode 100644 example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx delete mode 100644 example/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx delete mode 100644 example/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx delete mode 100644 example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx delete mode 100644 example/src/components/atoms/index.ts delete mode 100644 example/src/components/molecules/BleDevice/BleDevice.styled.tsx delete mode 100644 example/src/components/molecules/BleDevice/BleDevice.tsx delete mode 100644 example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx delete mode 100644 example/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx delete mode 100644 example/src/components/molecules/index.ts delete mode 100644 example/src/consts/nRFDeviceConsts.ts delete mode 100644 example/src/navigation/components/commonScreenOptions.tsx delete mode 100644 example/src/navigation/components/index.ts delete mode 100644 example/src/navigation/index.ts delete mode 100644 example/src/navigation/navigation.tsx delete mode 100644 example/src/navigation/navigators/MainStack.tsx delete mode 100644 example/src/navigation/navigators/index.ts create mode 100644 example/src/screens/CharacteristicScreen.tsx create mode 100644 example/src/screens/DeviceScreen.tsx delete mode 100644 example/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx delete mode 100644 example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx delete mode 100644 example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx delete mode 100644 example/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx delete mode 100644 example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx delete mode 100644 example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx delete mode 100644 example/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx delete mode 100644 example/src/screens/MainStack/InstanceDestroyScreen/utils.ts delete mode 100644 example/src/screens/MainStack/index.ts create mode 100644 example/src/screens/ScanScreen.tsx delete mode 100644 example/src/screens/index.ts delete mode 100644 example/src/services/BLEService/BLEService.ts delete mode 100644 example/src/services/index.ts delete mode 100644 example/src/services/storage/persistentDeviceName.ts delete mode 100644 example/src/theme/colors.ts delete mode 100644 example/src/theme/sizes.ts delete mode 100644 example/src/theme/theme.ts delete mode 100644 example/src/types/TestStateType.ts delete mode 100644 example/src/types/index.ts delete mode 100644 example/src/utils/cloneDeep.ts delete mode 100644 example/src/utils/getCurrentTimeAsBase64.ts delete mode 100644 example/src/utils/getDateAsBase64.ts delete mode 100644 example/src/utils/getDateUint8Array.ts delete mode 100644 example/src/utils/isAndroidAbove14.ts delete mode 100644 example/src/utils/wait.ts create mode 100644 example/tsconfig.json delete mode 100644 example/yarn.lock create mode 100644 ios/react_native_ble_plx.h diff --git a/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt index b33625e1..35dd2f59 100644 --- a/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt +++ b/android/src/main/kotlin/com/bleplx/BleManagerWrapper.kt @@ -190,6 +190,9 @@ class BleManagerWrapper(context: Context) : BleManager(context) { 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 @@ -197,14 +200,12 @@ class BleManagerWrapper(context: Context) : BleManager(context) { if (hasIndicate) { enableIndications(char) - .with(callback) .fail { _, status -> close(GattException(status, "Enable indications failed for $characteristicUuid")) } .enqueue() } else if (hasNotify) { enableNotifications(char) - .with(callback) .fail { _, status -> close(GattException(status, "Enable notifications failed for $characteristicUuid")) } diff --git a/example/.eslintrc.js b/example/.eslintrc.js new file mode 100644 index 00000000..187894b6 --- /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 00000000..de999559 --- /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 00000000..06860c8d --- /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 00000000..834a527f --- /dev/null +++ b/example/App.tsx @@ -0,0 +1 @@ +export { default } from './src/App'; diff --git a/example/Gemfile b/example/Gemfile index da4befef..6a4c5f17 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 3ba40862..00000000 --- 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 8bd066df..3e2c3f85 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 00000000..e532f701 --- /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 86c1a37d..3a876346 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 4b185bc1..00000000 --- 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 16bf5a3e..fb78f397 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 d435cfae..7ba83a2a 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 cd0e01a1..dad99b02 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 a46a5b90..9afe6159 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 7f93135c49b765f8051ef9d0a6055ff8e46073d8..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 79eb9d00..37f853b1 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 f5feea6d..ef07e016 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 7b21da3c..7530e8d8 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 6d44d916..32a4c710 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 7480acd1..f7b3da3b 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 0c68f651..00000000 --- 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 2c628e7b..00000000 --- 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 2938510f..9b739329 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 2a2cc18d..00000000 --- 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 e11d920b..00000000 --- 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 7af66a7e..4aac5fdf 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,27 @@ /* 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; ENABLE_BITCODE = NO; INFOPLIST_FILE = BlePlxExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -390,10 +273,12 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.intent.BlePlxExample; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.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 +289,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = 457FBQ4469; + CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = BlePlxExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -417,9 +302,11 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.intent.BlePlxExample; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.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 +315,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 +342,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 +352,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 +360,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 +372,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 +383,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 +397,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 +424,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 +446,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 +457,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 +470,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 18d98100..00000000 --- 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 00000000..a6a47a3c --- /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 fd2b8ad25d8ffeec8fb2d44459fc783279f1387d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1253 zcmVPx(pGibPR9HvtmwilCWgN#p&pB7diwh*cmz8BAXf`*|X0uI{g(y0FgETXhASGd0 zuGMC4uF`bVNjX=(#VoD0Xy2`=8f%X_}13F6# zR%Uw#cs^I5<}8#sy^j}_Iv6taE(4w_mpr@7Q^9%Zf{mM0Iy)dKQR9#6o*>019mK@k zWxxyPC39!@27GXY;Irc@rp;zF)I(aTPD@MA9#j;l{>foGQEXd$s|9Aw(3v_-5IYLm zT5oai&@oh{mG%zJN}ABnAex&ZS^eT1TrP#G3W=skcI;HGaXZmxz=j4WI$^MTr@eFEybr0_df^Ma0X zd+*1^!I9$*Dl4uqGi8spQdn3JW5y1qt+k7b7aHm6(uo)pLe06C(G3U5Q#G2J+)yE* zf=><#M#cFqJ3ayTe_*g_L+`8%4TTfM4ze=$QRe)W@bF+t%5oVt>>dlCs;Z7zGxnls z2CH%>vvARLGUgc=M$eu!i*~_>Dia^Qv*$hmlc(v_UGcmX%uZ|IzyUYJl*Ex7I=u8R z&s?r9CXCPLT4O8GF#{PL_Y%MS>}lzO423s+6mOq^OPMc?x-}?`*SAz;iW{tmQf_H+vL_ zjC5Q4`uZ;@c>gqtA~^8z(@c77I9;7O@4W5gy?4v43405kWb)%n>CVEcO1F|}a}_pkQD}E*jC;hbq7cGL6kRtkEjD9%N<8~NT8yq^ zC5c%H%rKQ(xk_Qc0NeW*uOl-x_rB#T9P^hlF zpNNP!E?t5f&61(R1m6^@eJ8v9rfooP-Hcgw4j)w+{(w74N{S8gatzDbzsH$s2Vr6M z7;OWVl}c7*c{U3np-@=j;PP*hMd_a7CRS0YikTdC!Zu*}NlEr{?*OyY%yZYEuFgBc z_hk+S54MAc{cC4!tt2(s6L3nRV8bSrh6c%+)jGdl?hO(i0nQ)(gKoym=GR`;IQlu( z*=#nT^V1c~W>vkN1Fkm7`gI1SrIMRBu-dRK+f(u!f9>2e0}2Kd^j86Y=LhznzY6{dTN*m2IUV!v P00000NkvXXu0mjfg_B*~ 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 1ff8bc71304dd554956ad84d87dec00e8e1ec82c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2363 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 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 d1ea0e198f5ded667cd3abdaf334b2e4e95009ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2176 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 OQvecSYev81lky)Z)fw{u 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 88180605a8e79b15b650f5fbe3cd573914dcf935..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5036 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 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 88180605a8e79b15b650f5fbe3cd573914dcf935..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5036 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 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 32f517b1c6446b7eb5fb13d6aa40521b02a5e48f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7056 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 diff --git a/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/Contents.json b/example/ios/BlePlxExample/Images.xcassets/AppIcon.appiconset/Contents.json index eccc7eb1..81213230 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 e18d713a..73864d37 100644 --- a/example/ios/BlePlxExample/Info.plist +++ b/example/ios/BlePlxExample/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion en CFBundleDisplayName @@ -31,10 +33,10 @@ NSAllowsLocalNetworking - NSBluetoothAlwaysUsageDescription - Our app uses bluetooth to find, connect and transfer data between different devices NSLocationWhenInUseUsageDescription + RCTNewArchEnabled + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -44,8 +46,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 3222da5d..00000000 --- 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 fe022f15..fa3d9086 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,7 +24,6 @@ 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], diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 2ed71dd0..8f2a6521 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: 3ade5fe23e99250c4107caf2b89c4a1ad7d37562 + 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: b2563173982d3851c958f079c214a450847cc335 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/jest.config.js b/example/jest.config.js index 3d869b20..8eb675e9 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 009b32e8..00000000 --- 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 0012cbac..e3329633 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -1,40 +1,23 @@ -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, '..'); /** * 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 - }, {}) + // Make sure Metro can resolve modules from the library root + nodeModulesPaths: [ + path.resolve(__dirname, 'node_modules'), + path.resolve(libraryRoot, 'node_modules'), + ], }, +}; - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true - } - }) - } -} - -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 00000000..528e8a72 --- /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 b38581fc..cc02001d 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 ef5b6b64..00000000 --- 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 ea87f883..88b1bc09 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,17 +1,66 @@ -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'; + +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; + }; + }; +}; + +const Stack = createNativeStackNavigator(); + +export default function App() { + const managerRef = useRef(new BleManager()); + + useEffect(() => { + const manager = managerRef.current; + manager.createClient().catch((e) => { + 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 864f4600..00000000 --- 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 4d0dc5c3..00000000 --- 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 a1b5419f..00000000 --- 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 0e00a165..00000000 --- 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 414a4eda..00000000 --- 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 9a2984bf..00000000 --- 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 c0e6ac12..00000000 --- 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 dd06f9c1..00000000 --- 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 cc2db6bc..00000000 --- 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 29366514..00000000 --- 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 3f14cec6..00000000 --- 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 d650a155..00000000 --- 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 a1084771..00000000 --- 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 c96d169d..00000000 --- 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 867904dc..00000000 --- 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 39d6d9ff..00000000 --- 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 66395ca6..00000000 --- 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 0c412e25..00000000 --- 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 2df86427..00000000 --- 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 9c8479a4..00000000 --- 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 28f689f4..00000000 --- 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 d10986d6..00000000 --- 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 472fafd1..00000000 --- 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 00000000..bc775fa7 --- /dev/null +++ b/example/src/screens/CharacteristicScreen.tsx @@ -0,0 +1,195 @@ +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; + +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 monitorSub = useRef(null); + + useEffect(() => { + return () => { + monitorSub.current?.remove(); + }; + }, []); + + const readCharacteristic = useCallback(async () => { + try { + const result = await manager.readCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + ); + setValue(result.value ?? '(null)'); + } 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, + ); + Alert.alert('Write', 'Value written successfully'); + } catch (e: any) { + Alert.alert('Write Error', e.message || String(e)); + } + }, [manager, deviceId, serviceUuid, characteristicUuid, writeValue, properties]); + + const toggleMonitor = useCallback( + (enabled: boolean) => { + if (enabled) { + monitorSub.current = manager.monitorCharacteristicForDevice( + deviceId, + serviceUuid, + characteristicUuid, + (error, event) => { + if (error) { + console.warn('Monitor error:', error); + setMonitoring(false); + return; + } + if (event) { + setValue(event.value); + } + }, + ); + setMonitoring(true); + } else { + monitorSub.current?.remove(); + monitorSub.current = null; + 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} + + + + {properties.isReadable && ( + + Read + + )} + + {(properties.isWritableWithResponse || + properties.isWritableWithoutResponse) && ( + + + + Write + + + )} + + {(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' }, + 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, + }, + monitorRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 16, + paddingVertical: 8, + }, + monitorLabel: { fontSize: 16, fontWeight: '600' }, +}); diff --git a/example/src/screens/DeviceScreen.tsx b/example/src/screens/DeviceScreen.tsx new file mode 100644 index 00000000..2d102d5a --- /dev/null +++ b/example/src/screens/DeviceScreen.tsx @@ -0,0 +1,157 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + FlatList, + StyleSheet, + Alert, +} from 'react-native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { CharacteristicInfo } from 'react-native-ble-plx'; +import type { RootStackParamList } from '../App'; + +type Props = NativeStackScreenProps; + +interface ServiceGroup { + serviceUuid: string; + characteristics: CharacteristicInfo[]; +} + +export default function DeviceScreen({ navigation, route }: Props) { + const { manager, deviceId, deviceName } = route.params; + const [mtu, setMtu] = useState(null); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [services, _setServices] = useState([]); + const [discovering, setDiscovering] = useState(false); + + useEffect(() => { + manager.getMtu(deviceId).then(setMtu).catch(() => {}); + }, [manager, deviceId]); + + const discoverServices = useCallback(async () => { + setDiscovering(true); + try { + await manager.discoverAllServicesAndCharacteristics(deviceId); + // The v4 API returns DeviceInfo from discover, but characteristics + // need to be read separately. For now we show the discovery was successful. + // In a full implementation we'd query for services/characteristics. + Alert.alert('Discovery', 'Services and characteristics discovered. Characteristic browsing requires servicesForDevice() API (not yet in v4 spec).'); + } catch (e: any) { + Alert.alert('Discovery Error', e.message || String(e)); + } finally { + setDiscovering(false); + } + }, [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]); + + return ( + + + {deviceName || 'Unknown Device'} + ID: {deviceId} + {mtu != null && MTU: {mtu}} + + + + + + {discovering ? 'Discovering...' : 'Discover Services'} + + + + + Disconnect + + + + {services.length > 0 && ( + item.serviceUuid} + renderItem={({ item }) => ( + + Service: {item.serviceUuid} + {item.characteristics.map((char) => ( + + navigation.navigate('Characteristic', { + manager, + deviceId, + serviceUuid: item.serviceUuid, + characteristicUuid: char.uuid, + properties: { + isReadable: char.isReadable, + isWritableWithResponse: char.isWritableWithResponse, + isWritableWithoutResponse: char.isWritableWithoutResponse, + isNotifying: char.isNotifying, + isIndicatable: char.isIndicatable, + }, + }) + }> + {char.uuid} + + {[ + char.isReadable && 'Read', + (char.isWritableWithResponse || char.isWritableWithoutResponse) && 'Write', + char.isNotifying && 'Notify', + char.isIndicatable && 'Indicate', + ] + .filter(Boolean) + .join(', ')} + + + ))} + + )} + /> + )} + + ); +} + +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 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + buttonDisabled: { backgroundColor: '#ccc' }, + disconnectButton: { backgroundColor: '#FF3B30' }, + buttonText: { color: '#fff', fontWeight: '600' }, + serviceGroup: { marginBottom: 16 }, + serviceUuid: { fontSize: 14, fontWeight: '600', marginBottom: 6 }, + charRow: { + paddingVertical: 8, + paddingHorizontal: 12, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + charUuid: { fontSize: 13 }, + charProps: { fontSize: 11, color: '#888', marginTop: 2 }, +}); 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 375a0550..00000000 --- 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 229fe96c..00000000 --- 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 bab6d54c..00000000 --- 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 99f21d89..00000000 --- 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 5ae8b94f..00000000 --- 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 35b70499..00000000 --- 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 8cf621ad..00000000 --- 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 8f73e0f7..00000000 --- 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 6887aa49..00000000 --- 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 00000000..2f2cad08 --- /dev/null +++ b/example/src/screens/ScanScreen.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useState, useCallback, useRef } from 'react'; +import { + View, + Text, + FlatList, + TouchableOpacity, + 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 devicesRef = useRef>(new Map()); + + useEffect(() => { + const sub = manager.onStateChange((state: string) => { + setBleState(state); + }, true); + + requestAndroidPermissions().then((ok) => { + if (!ok) { + Alert.alert('Permissions', 'BLE permissions not granted'); + } + }); + + return () => sub.remove(); + }, [manager]); + + const startScan = useCallback(() => { + devicesRef.current = new Map(); + setDevices(new Map()); + setScanning(true); + + manager.startDeviceScan(null, null, (error, result) => { + if (error) { + console.warn('Scan error:', 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 (deviceId: string) => { + stopScan(); + try { + const device = await manager.connectToDevice(deviceId); + navigation.navigate('Device', { manager, deviceId: device.id, deviceName: device.name }); + } catch (e: any) { + Alert.alert('Connection Error', e.message || String(e)); + } + }, + [manager, navigation, stopScan], + ); + + const deviceList = Array.from(devices.values()); + + return ( + + BLE State: {bleState} + + + + Start Scan + + + Stop Scan + + + + + {deviceList.length} device(s) found + + + item.id} + renderItem={({ item }) => ( + connectToDevice(item.id)}> + + {item.name || 'Unknown Device'} + + {item.id} + RSSI: {item.rssi} + + )} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, backgroundColor: '#fff' }, + stateText: { fontSize: 16, fontWeight: '600', marginBottom: 12 }, + buttonRow: { flexDirection: 'row', gap: 12, marginBottom: 12 }, + button: { + backgroundColor: '#007AFF', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + buttonDisabled: { backgroundColor: '#ccc' }, + buttonText: { color: '#fff', fontWeight: '600' }, + countText: { fontSize: 14, color: '#666', marginBottom: 8 }, + deviceRow: { + padding: 12, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + deviceName: { fontSize: 16, fontWeight: '500' }, + deviceId: { fontSize: 12, color: '#888', marginTop: 2 }, + deviceRssi: { fontSize: 12, color: '#888', marginTop: 2 }, +}); diff --git a/example/src/screens/index.ts b/example/src/screens/index.ts deleted file mode 100644 index 472fafd1..00000000 --- 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 093ead7e..00000000 --- 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 ec06f798..00000000 --- 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 4818b1b3..00000000 --- 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 7679fd50..00000000 --- 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 7c2ad4bf..00000000 --- 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 d5b1621b..00000000 --- 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 93465a34..00000000 --- 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 3689e2fc..00000000 --- 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 93851e47..00000000 --- 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 722beab2..00000000 --- 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 cc091c08..00000000 --- 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 6770bc11..00000000 --- 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 57530046..00000000 --- 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 44bede3a..00000000 --- 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 00000000..266ba9ca --- /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 c239b0d5..00000000 --- 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/ios/BLEActor.swift b/ios/BLEActor.swift index 4427f627..11de8039 100644 --- a/ios/BLEActor.swift +++ b/ios/BLEActor.swift @@ -6,7 +6,7 @@ import Foundation /// Central manager actor with custom executor pinned to the CoreBluetooth queue. /// All CBCentralManager and CBPeripheral interactions happen on this queue. actor BLEActor { - let queue = DispatchQueue(label: "com.bleplx.ble") + let queue = DispatchSerialQueue(label: "com.bleplx.ble") nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } private var centralManager: CBCentralManager! diff --git a/ios/BLEModuleImpl.swift b/ios/BLEModuleImpl.swift index b94005d8..35896d4e 100644 --- a/ios/BLEModuleImpl.swift +++ b/ios/BLEModuleImpl.swift @@ -2,23 +2,18 @@ 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 sendEvent(withName name: String, body: Any?) -} - -// MARK: - Event names - -private enum EventName { - static let scanResult = "onScanResult" - static let connectionStateChange = "onConnectionStateChange" - static let characteristicValueUpdate = "onCharacteristicValueUpdate" - static let stateChange = "onStateChange" - static let restoreState = "onRestoreState" - static let error = "onError" - static let bondStateChange = "onBondStateChange" - static let connectionEvent = "onConnectionEvent" - static let l2capData = "onL2CAPData" - static let l2capClose = "onL2CAPClose" + 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 @@ -27,42 +22,41 @@ private enum EventName { /// All methods are called from the ObjC++ layer and delegate to BLEActor. @objc public class BLEModuleImpl: NSObject { - private var eventEmitterAdapter: EventEmitterAdapter? - private var eventEmitter: BLEEventEmitter? { eventEmitterAdapter } + private weak var eventEmitter: BLEEventEmitter? private var actor: BLEActor? - @objc public init(eventEmitter: RCTEventEmitter) { - self.eventEmitterAdapter = EventEmitterAdapter(emitter: eventEmitter) + @objc public init(eventEmitter: BLEEventEmitter) { + self.eventEmitter = eventEmitter super.init() } @objc public static func supportedEventNames() -> [String] { return [ - EventName.scanResult, - EventName.connectionStateChange, - EventName.characteristicValueUpdate, - EventName.stateChange, - EventName.restoreState, - EventName.error, - EventName.bondStateChange, - EventName.connectionEvent, - EventName.l2capData, - EventName.l2capClose, + "onScanResult", + "onConnectionStateChange", + "onCharacteristicValueUpdate", + "onStateChange", + "onRestoreState", + "onError", + "onBondStateChange", + "onConnectionEvent", + "onL2CAPData", + "onL2CAPClose", ] } // MARK: - Private helpers - private func sendEvent(_ name: String, body: Any?) { - eventEmitter?.sendEvent(withName: name, body: body) + private func emitError(_ dict: [String: Any]) { + eventEmitter?.emitOnError(dict as NSDictionary) } - private func rejectWithError(_ reject: @escaping RCTPromiseRejectBlock, error: BleError) { + private func rejectWithError(_ reject: @escaping @Sendable (String?, String?, Error?) -> Void, error: BleError) { reject(String(error.code.rawValue), error.message, nil) - sendEvent(EventName.error, body: error.toDictionary()) + emitError(error.toDictionary()) } - private func rejectWithError(_ reject: @escaping RCTPromiseRejectBlock, error: Error) { + private func rejectWithError(_ reject: @escaping @Sendable (String?, String?, Error?) -> Void, error: Error) { if let bleError = error as? BleError { rejectWithError(reject, error: bleError) } else { @@ -75,14 +69,14 @@ private enum EventName { @objc public func createClient( restoreStateIdentifier: String?, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { - let emitter = self + let emitter = self.eventEmitter actor = BLEActor( onScanResult: { [weak emitter] snapshot in - emitter?.sendEvent(EventName.scanResult, body: snapshot.toDictionary()) + emitter?.emitOnScanResult(snapshot.toDictionary() as NSDictionary) }, onConnectionStateChange: { [weak emitter] deviceId, state, error in var body: [String: Any] = [ @@ -91,7 +85,7 @@ private enum EventName { ] body["errorCode"] = error?.code.rawValue body["errorMessage"] = error?.message - emitter?.sendEvent(EventName.connectionStateChange, body: body) + emitter?.emitOnConnectionStateChange(body as NSDictionary) }, onCharacteristicValueUpdate: { [weak emitter] deviceId, serviceUuid, charUuid, value, transactionId in var body: [String: Any] = [ @@ -101,23 +95,23 @@ private enum EventName { "value": value ?? "", ] body["transactionId"] = transactionId - emitter?.sendEvent(EventName.characteristicValueUpdate, body: body) + emitter?.emitOnCharacteristicValueUpdate(body as NSDictionary) }, onStateChange: { [weak emitter] state in - emitter?.sendEvent(EventName.stateChange, body: ["state": state]) + emitter?.emitOnStateChange(["state": state] as NSDictionary) }, onRestoreState: { [weak emitter] devices in let deviceDicts = devices.map { $0.toDictionary() } - emitter?.sendEvent(EventName.restoreState, body: ["devices": deviceDicts]) + emitter?.emitOnRestoreState(["devices": deviceDicts] as NSDictionary) }, onError: { [weak emitter] error in - emitter?.sendEvent(EventName.error, body: error.toDictionary()) + emitter?.emitOnError(error.toDictionary() as NSDictionary) }, onL2CAPData: { [weak emitter] channelId, data in - emitter?.sendEvent(EventName.l2capData, body: ["channelId": channelId, "data": data]) + emitter?.emitOnL2CAPData(["channelId": channelId, "data": data] as NSDictionary) }, onL2CAPClose: { [weak emitter] channelId, error in - emitter?.sendEvent(EventName.l2capClose, body: ["channelId": channelId, "error": error as Any]) + emitter?.emitOnL2CAPClose(["channelId": channelId, "error": error as Any] as NSDictionary) } ) @@ -128,8 +122,8 @@ private enum EventName { } @objc public func destroyClient( - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { await actor?.destroyClient() @@ -148,8 +142,8 @@ private enum EventName { // MARK: - State @objc public func state( - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { let state = await actor?.state() ?? "Unknown" @@ -172,8 +166,8 @@ private enum EventName { } @objc public func stopDeviceScan( - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { await actor?.stopDeviceScan() @@ -186,8 +180,8 @@ private enum EventName { @objc public func connectToDevice( deviceId: String, options: NSDictionary?, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { let opts = options as? [String: Any] @@ -206,8 +200,8 @@ private enum EventName { @objc public func cancelDeviceConnection( deviceId: String, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -224,8 +218,8 @@ private enum EventName { @objc public func isDeviceConnected( deviceId: String, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -245,8 +239,8 @@ private enum EventName { @objc public func discoverAllServicesAndCharacteristics( deviceId: String, transactionId: String?, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -268,8 +262,8 @@ private enum EventName { serviceUuid: String, characteristicUuid: String, transactionId: String?, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -296,8 +290,8 @@ private enum EventName { value: String, withResponse: Bool, transactionId: String?, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -344,7 +338,7 @@ private enum EventName { ) } catch { if let bleError = error as? BleError { - sendEvent(EventName.error, body: bleError.toDictionary()) + emitError(bleError.toDictionary()) } } } @@ -354,8 +348,8 @@ private enum EventName { @objc public func getMtu( deviceId: String, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -374,8 +368,8 @@ private enum EventName { deviceId: String, mtu: NSInteger, transactionId: String?, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -396,8 +390,8 @@ private enum EventName { deviceId: String, txPhy: NSInteger, rxPhy: NSInteger, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { // PHY selection is not available on iOS Task { @@ -416,8 +410,8 @@ private enum EventName { @objc public func readPhy( deviceId: String, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + 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) @@ -428,8 +422,8 @@ private enum EventName { @objc public func requestConnectionPriority( deviceId: String, priority: NSInteger, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { // Connection priority is an Android-only concept reject(String(BleErrorCode.operationStartFailed.rawValue), @@ -441,8 +435,8 @@ private enum EventName { @objc public func openL2CAPChannel( deviceId: String, psm: NSInteger, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -460,8 +454,8 @@ private enum EventName { @objc public func writeL2CAPChannel( channelId: NSInteger, data: String, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -481,8 +475,8 @@ private enum EventName { @objc public func closeL2CAPChannel( channelId: NSInteger, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { do { @@ -500,8 +494,8 @@ private enum EventName { // MARK: - Bonding (Limited on iOS) @objc public func getBondedDevices( - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { // iOS doesn't expose bonded device list via CoreBluetooth resolve([]) @@ -510,8 +504,8 @@ private enum EventName { // MARK: - Authorization @objc public func getAuthorizationStatus( - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { if #available(iOS 13.1, *) { switch CBManager.authorization { @@ -536,8 +530,8 @@ private enum EventName { @objc public func cancelTransaction( transactionId: String, - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock + resolve: @escaping @Sendable (Any?) -> Void, + reject: @escaping @Sendable (String?, String?, Error?) -> Void ) { Task { await actor?.cancelTransaction(transactionId) @@ -546,18 +540,3 @@ private enum EventName { } } -// MARK: - RCTEventEmitter adapter - -/// Wraps an RCTEventEmitter (from ObjC) to conform to BLEEventEmitter. -/// Since RCTEventEmitter is an ObjC class, we use a thin wrapper. -class EventEmitterAdapter: BLEEventEmitter { - private weak var emitter: RCTEventEmitter? - - init(emitter: RCTEventEmitter) { - self.emitter = emitter - } - - func sendEvent(withName name: String, body: Any?) { - emitter?.sendEvent(withName: name, body: body) - } -} diff --git a/ios/BlePlx.mm b/ios/BlePlx.mm index c425ae8d..06b1fe6b 100644 --- a/ios/BlePlx.mm +++ b/ios/BlePlx.mm @@ -7,10 +7,7 @@ #import #import - -#ifdef RCT_NEW_ARCH_ENABLED #import -#endif // Auto-generated Swift bridge header #if __has_include("react_native_ble_plx-Swift.h") @@ -19,15 +16,8 @@ #import #endif -#ifdef RCT_NEW_ARCH_ENABLED -@interface BlePlx : NativeBlePlxSpec -@end -#else -#import -#import -@interface BlePlx : RCTEventEmitter +@interface BlePlx : NativeBlePlxSpecBase @end -#endif @implementation BlePlx { BLEModuleImpl *_impl; @@ -47,199 +37,192 @@ + (BOOL)requiresMainQueueSetup { return NO; } -- (void)invalidate { +- (void)dealloc { [_impl invalidate]; - [super invalidate]; -} - -- (NSArray *)supportedEvents { - return [BLEModuleImpl supportedEventNames]; } // MARK: - Lifecycle -RCT_EXPORT_METHOD(createClient:(NSString *)restoreStateIdentifier - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)createClient:(NSString *)restoreStateIdentifier + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl createClientWithRestoreStateIdentifier:restoreStateIdentifier resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(destroyClient:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)destroyClient:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl destroyClientWithResolve:resolve reject:reject]; } // MARK: - State -RCT_EXPORT_METHOD(state:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)state:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl stateWithResolve:resolve reject:reject]; } // MARK: - Scanning -RCT_EXPORT_METHOD(startDeviceScan:(NSArray *)uuids - options:(NSDictionary *)options) { +- (void)startDeviceScan:(NSArray *)uuids + options:(NSDictionary *)options { [_impl startDeviceScanWithUuids:uuids options:options]; } -RCT_EXPORT_METHOD(stopDeviceScan:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)stopDeviceScan:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl stopDeviceScanWithResolve:resolve reject:reject]; } // MARK: - Connection -RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId - options:(NSDictionary *)options - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)connectToDevice:(NSString *)deviceId + options:(NSDictionary *)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl connectToDeviceWithDeviceId:deviceId options:options resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(cancelDeviceConnection:(NSString *)deviceId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)cancelDeviceConnection:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl cancelDeviceConnectionWithDeviceId:deviceId resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(isDeviceConnected:(NSString *)deviceId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)isDeviceConnected:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl isDeviceConnectedWithDeviceId:deviceId resolve:resolve reject:reject]; } // MARK: - Discovery -RCT_EXPORT_METHOD(discoverAllServicesAndCharacteristics:(NSString *)deviceId - transactionId:(NSString *)transactionId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)discoverAllServicesAndCharacteristics:(NSString *)deviceId + transactionId:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl discoverAllServicesAndCharacteristicsWithDeviceId:deviceId transactionId:transactionId resolve:resolve reject:reject]; } // MARK: - Read/Write -RCT_EXPORT_METHOD(readCharacteristic:(NSString *)deviceId - serviceUuid:(NSString *)serviceUuid - characteristicUuid:(NSString *)characteristicUuid - transactionId:(NSString *)transactionId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)readCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + transactionId:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl readCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid transactionId:transactionId resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(writeCharacteristic:(NSString *)deviceId - serviceUuid:(NSString *)serviceUuid - characteristicUuid:(NSString *)characteristicUuid - value:(NSString *)value - withResponse:(BOOL)withResponse - transactionId:(NSString *)transactionId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)writeCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + value:(NSString *)value + withResponse:(BOOL)withResponse + transactionId:(NSString *)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 -RCT_EXPORT_METHOD(monitorCharacteristic:(NSString *)deviceId - serviceUuid:(NSString *)serviceUuid - characteristicUuid:(NSString *)characteristicUuid - subscriptionType:(NSString *)subscriptionType - transactionId:(NSString *)transactionId) { +- (void)monitorCharacteristic:(NSString *)deviceId + serviceUuid:(NSString *)serviceUuid + characteristicUuid:(NSString *)characteristicUuid + subscriptionType:(NSString *)subscriptionType + transactionId:(NSString *)transactionId { [_impl monitorCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid subscriptionType:subscriptionType transactionId:transactionId]; } // MARK: - MTU -RCT_EXPORT_METHOD(getMtu:(NSString *)deviceId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)getMtu:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl getMtuWithDeviceId:deviceId resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(requestMtu:(NSString *)deviceId - mtu:(double)mtu - transactionId:(NSString *)transactionId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)requestMtu:(NSString *)deviceId + mtu:(double)mtu + transactionId:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl requestMtuWithDeviceId:deviceId mtu:(NSInteger)mtu transactionId:transactionId resolve:resolve reject:reject]; } // MARK: - PHY -RCT_EXPORT_METHOD(requestPhy:(NSString *)deviceId - txPhy:(double)txPhy - rxPhy:(double)rxPhy - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (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]; } -RCT_EXPORT_METHOD(readPhy:(NSString *)deviceId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)readPhy:(NSString *)deviceId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl readPhyWithDeviceId:deviceId resolve:resolve reject:reject]; } // MARK: - Connection Priority -RCT_EXPORT_METHOD(requestConnectionPriority:(NSString *)deviceId - priority:(double)priority - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (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 -RCT_EXPORT_METHOD(openL2CAPChannel:(NSString *)deviceId - psm:(double)psm - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)openL2CAPChannel:(NSString *)deviceId + psm:(double)psm + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl openL2CAPChannelWithDeviceId:deviceId psm:(NSInteger)psm resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(writeL2CAPChannel:(double)channelId - data:(NSString *)data - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)writeL2CAPChannel:(double)channelId + data:(NSString *)data + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl writeL2CAPChannelWithChannelId:(NSInteger)channelId data:data resolve:resolve reject:reject]; } -RCT_EXPORT_METHOD(closeL2CAPChannel:(double)channelId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)closeL2CAPChannel:(double)channelId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl closeL2CAPChannelWithChannelId:(NSInteger)channelId resolve:resolve reject:reject]; } // MARK: - Bonding -RCT_EXPORT_METHOD(getBondedDevices:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)getBondedDevices:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl getBondedDevicesWithResolve:resolve reject:reject]; } // MARK: - Authorization -RCT_EXPORT_METHOD(getAuthorizationStatus:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)getAuthorizationStatus:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl getAuthorizationStatusWithResolve:resolve reject:reject]; } // MARK: - Cancellation -RCT_EXPORT_METHOD(cancelTransaction:(NSString *)transactionId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { +- (void)cancelTransaction:(NSString *)transactionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [_impl cancelTransactionWithTransactionId:transactionId resolve:resolve reject:reject]; } -#ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } -#endif @end diff --git a/ios/PeripheralWrapper.swift b/ios/PeripheralWrapper.swift index 4ebd2937..96836e15 100644 --- a/ios/PeripheralWrapper.swift +++ b/ios/PeripheralWrapper.swift @@ -6,7 +6,7 @@ import Foundation /// 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: DispatchQueue + let queue: DispatchSerialQueue nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } private let peripheral: CBPeripheral @@ -19,7 +19,7 @@ actor PeripheralWrapper { delegate.characteristicUpdateStream } - init(peripheral: CBPeripheral, queue: DispatchQueue) { + init(peripheral: CBPeripheral, queue: DispatchSerialQueue) { self.peripheral = peripheral self.queue = queue self.deviceId = peripheral.identifier.uuidString diff --git a/ios/ScanManager.swift b/ios/ScanManager.swift index dbc42609..98fb5fde 100644 --- a/ios/ScanManager.swift +++ b/ios/ScanManager.swift @@ -9,10 +9,10 @@ actor ScanManager { private var isScanning = false private var scanTask: Task? - let queue: DispatchQueue + let queue: DispatchSerialQueue nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } - init(queue: DispatchQueue) { + init(queue: DispatchSerialQueue) { self.queue = queue } diff --git a/ios/StateRestoration.swift b/ios/StateRestoration.swift index 8f3d0496..b448aa00 100644 --- a/ios/StateRestoration.swift +++ b/ios/StateRestoration.swift @@ -56,10 +56,10 @@ actor StateRestoration { private var bufferedRestorationData: [RestorationState] = [] private var hasJSSubscribed = false - let queue: DispatchQueue + let queue: DispatchSerialQueue nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } - init(queue: DispatchQueue) { + init(queue: DispatchSerialQueue) { self.queue = queue } diff --git a/ios/react_native_ble_plx.h b/ios/react_native_ble_plx.h new file mode 100644 index 00000000..6f2ed7ce --- /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/react-native-ble-plx.podspec b/react-native-ble-plx.podspec index 74854957..90089307 100644 --- a/react-native-ble-plx.podspec +++ b/react-native-ble-plx.podspec @@ -11,17 +11,17 @@ Pod::Spec.new do |s| s.license = package["license"] s.authors = package["author"] - s.platforms = { :ios => "14.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,swift}" + s.exclude_files = "ios/BlePlx-Bridging-Header.h" s.swift_version = "5.9" s.frameworks = "CoreBluetooth" s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES", - "SWIFT_OBJC_BRIDGING_HEADER" => "$(PODS_TARGET_SRCROOT)/ios/BlePlx-Bridging-Header.h", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" } From 3292720fb9e3c7f47ef4a793155ebfc305b363f7 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 15:45:17 -0500 Subject: [PATCH 29/36] test: update Maestro flows with real testIDs and add README Replace placeholder element IDs in all three existing Maestro flow files with the actual testID values from the example app screens (ScanScreen, DeviceScreen, CharacteristicScreen). Add the missing write-read-roundtrip flow and a README documenting prerequisites, permission pre-grant commands, and the testID reference table. Key changes: - scan-start-btn / scan-stop-btn replace start-scan-button - disconnect-btn replaces disconnect-button - discover-btn replaces implicit service-discovery step - monitor-toggle (Switch) replaces stop-monitor-button - value-display replaces char-value / indicate-count-label - char-fff1/fff2/fff3 used for known firmware characteristic UUIDs - disconnect-recovery updated to match app navigation (goBack to ScanScreen) - indicate-stress updated to use CharacteristicScreen monitor-toggle pattern --- integration-tests/hardware/maestro/README.md | 127 +++++++++++++ .../hardware/maestro/disconnect-recovery.yaml | 96 +++++----- .../hardware/maestro/indicate-stress.yaml | 175 +++++++++++------- .../hardware/maestro/scan-pair-sync.yaml | 112 +++++++---- .../maestro/write-read-roundtrip.yaml | 121 ++++++++++++ 5 files changed, 473 insertions(+), 158 deletions(-) create mode 100644 integration-tests/hardware/maestro/README.md create mode 100644 integration-tests/hardware/maestro/write-read-roundtrip.yaml diff --git a/integration-tests/hardware/maestro/README.md b/integration-tests/hardware/maestro/README.md new file mode 100644 index 00000000..dd22e39d --- /dev/null +++ b/integration-tests/hardware/maestro/README.md @@ -0,0 +1,127 @@ +# Maestro Hardware Integration Tests + +These flows test the react-native-ble-plx library against real BLE hardware +using the [Maestro](https://maestro.mobile.dev) mobile UI testing framework. + +## Prerequisites + +### Hardware + +- A **BlePlxTest** peripheral must be flashed with the test firmware and powered + on within BLE range of the device under test. +- The firmware is located in `integration-tests/hardware/peripheral-firmware/`. + +### App + +- The example app (`example/`) must be built and installed on the test device. +- Android package: `com.bleplxexample` +- iOS bundle ID: `org.reactjs.native.example.BlePlxExample` + +### BLE Permissions + +BLE permissions must be granted before the flows run. Maestro does not handle +system permission dialogs reliably on all platforms. + +#### Android (API 31+) + +Run the following `adb` commands after installing the app: + +```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 +``` + +For Android API 28–30 (legacy location permission only): + +```bash +adb shell pm grant com.bleplxexample android.permission.ACCESS_FINE_LOCATION +``` + +#### iOS + +iOS BLE (`NSBluetoothAlwaysUsageDescription`) and location permissions must be +granted manually on first launch. Open the app on the device, accept all +permission prompts, then terminate and re-launch before running the flows. + +Alternatively, if using an iOS simulator with a paired BLE adapter, no +permission prompt appears. + +## Running the Flows + +Ensure the `maestro` CLI is installed (`brew install maestro` on macOS). + +### Individual flows + +```bash +# Full happy path: scan, connect, read characteristic, disconnect +maestro test integration-tests/hardware/maestro/scan-pair-sync.yaml + +# Write a value to echo characteristic and read it back +maestro test integration-tests/hardware/maestro/write-read-roundtrip.yaml + +# Simulate unexpected disconnection and verify recovery +maestro test integration-tests/hardware/maestro/disconnect-recovery.yaml + +# Sustained indication stream for 30 seconds +maestro test integration-tests/hardware/maestro/indicate-stress.yaml +``` + +### All flows in sequence + +```bash +maestro test integration-tests/hardware/maestro/ +``` + +## Flow Descriptions + +| File | What it tests | +|------|---------------| +| `scan-pair-sync.yaml` | Full happy path: BLE scan, connect, discover, read, disconnect | +| `write-read-roundtrip.yaml` | Write to echo characteristic and verify read-back matches | +| `disconnect-recovery.yaml` | Unexpected peripheral disconnect; re-scan and reconnect | +| `indicate-stress.yaml` | 30-second indication stream stress test on CharacteristicScreen | + +## TestIDs Reference + +The example app uses the following `testID` props for Maestro assertions: + +**ScanScreen** (`example/src/screens/ScanScreen.tsx`) + +| testID | Element | +|--------|---------| +| `scan-start-btn` | Start Scan button | +| `scan-stop-btn` | Stop Scan button | +| `device-list` | FlatList of discovered devices | +| `device-item-{id}` | Individual device row (id = MAC address or UUID) | + +**DeviceScreen** (`example/src/screens/DeviceScreen.tsx`) + +| testID | Element | +|--------|---------| +| `discover-btn` | Discover Services button | +| `disconnect-btn` | Disconnect button | +| `service-{uuid}` | Service group container (populated after discovery) | +| `char-{uuid}` | Characteristic row (tap to navigate to CharacteristicScreen) | + +**CharacteristicScreen** (`example/src/screens/CharacteristicScreen.tsx`) + +| testID | Element | +|--------|---------| +| `read-btn` | Read button (visible when characteristic is readable) | +| `write-input` | TextInput for base64 write value | +| `write-btn` | Write button (visible when characteristic is writable) | +| `monitor-toggle` | Switch to enable/disable notifications or indications | +| `value-display` | Text showing the current characteristic value | + +## Notes + +- The `disconnect-recovery.yaml` flow requires a manual or automated step to + trigger an unexpected disconnection from the firmware side. Annotated + comments in the flow indicate where to insert a relay command or serial call. +- The `write-read-roundtrip.yaml` flow uses base64-encoded values. "Hello" + encodes to `SGVsbG8=`. +- Discovery on DeviceScreen currently shows an alert because the v4 API's + `servicesForDevice()` is not yet implemented; characteristic rows (char-{uuid}) + only appear once that API is available. Until then, flows that navigate to a + characteristic require the service list to be populated by the firmware. diff --git a/integration-tests/hardware/maestro/disconnect-recovery.yaml b/integration-tests/hardware/maestro/disconnect-recovery.yaml index adf49031..fe99bfbe 100644 --- a/integration-tests/hardware/maestro/disconnect-recovery.yaml +++ b/integration-tests/hardware/maestro/disconnect-recovery.yaml @@ -1,18 +1,15 @@ # Maestro flow: Disconnect Recovery # -# This flow verifies that the library correctly handles an unexpected -# disconnection (e.g., the peripheral goes out of range or is powered off) -# and that the app can re-connect successfully after 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 initially. -# - The tester (or an automated relay) powers off the peripheral during -# the test to simulate an unexpected disconnection. -# - The test app is installed and foregrounded. -# -# Note: The "power off peripheral" step below is annotated as a manual action. -# When running on a device farm with controllable hardware, replace the -# instructional comment with the relevant relay/GPIO command. +# - 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 @@ -22,63 +19,71 @@ appId: com.bleplxexample --- # --------------------------------------------------------------------------- -# 1. Launch and connect to the peripheral +# 1. Launch and scan # --------------------------------------------------------------------------- - launchApp: clearState: true -- assertVisible: "Start Scan" +- assertVisible: + id: "scan-start-btn" + timeout: 5000 + +- assertVisible: + text: "PoweredOn" + timeout: 10000 - tapOn: - id: "start-scan-button" + id: "scan-start-btn" - assertVisible: text: "BlePlxTest" timeout: 15000 +# --------------------------------------------------------------------------- +# 2. Connect to BlePlxTest +# --------------------------------------------------------------------------- - tapOn: text: "BlePlxTest" +# Confirm we have reached DeviceScreen (connected state) - assertVisible: - id: "connection-state-label" - text: "connected" + id: "discover-btn" timeout: 10000 -# --------------------------------------------------------------------------- -# 2. Verify steady-state: receiving notifications -# --------------------------------------------------------------------------- - assertVisible: - id: "char-value" - timeout: 5000 + id: "disconnect-btn" # --------------------------------------------------------------------------- -# 3. Simulate unexpected disconnection +# 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')} # -# MANUAL STEP: power off the BlePlxTest peripheral now. -# When using a BLE relay board, insert the relay-off command here, e.g.: -# - evalScript: ${relay.off('BlePlxTest')} +# When running manually: power off or reset the peripheral now. # --------------------------------------------------------------------------- -# The library should detect the link loss and fire onDeviceDisconnected. -# The app displays the disconnected state. +# The library detects link loss via onDeviceDisconnected. The app calls +# navigation.goBack(), returning to ScanScreen. - assertVisible: - id: "connection-state-label" - text: "disconnected" + id: "scan-start-btn" timeout: 15000 -# The app should also show an error label since this was unexpected -- assertVisible: - id: "error-label" - timeout: 3000 +# The scan-start-btn being visible confirms we are back on ScanScreen, +# indicating the app detected the disconnection and navigated back. # --------------------------------------------------------------------------- -# 4. Recovery: power on the peripheral and reconnect +# 4. Re-scan and reconnect after the peripheral is back up # -# MANUAL STEP: power on the BlePlxTest peripheral now. +# MANUAL / AUTOMATION STEP: +# If the peripheral was powered off, power it back on now before this step. # --------------------------------------------------------------------------- - - tapOn: - id: "start-scan-button" + id: "scan-start-btn" - assertVisible: text: "BlePlxTest" @@ -88,22 +93,15 @@ appId: com.bleplxexample text: "BlePlxTest" - assertVisible: - id: "connection-state-label" - text: "connected" + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- -# 5. Verify notifications resume after re-connect +# 5. Clean disconnect # --------------------------------------------------------------------------- -- assertVisible: - id: "char-value" - timeout: 5000 - -# Clean disconnect - tapOn: - id: "disconnect-button" + id: "disconnect-btn" - assertVisible: - id: "connection-state-label" - text: "disconnected" - timeout: 5000 + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/indicate-stress.yaml b/integration-tests/hardware/maestro/indicate-stress.yaml index 3f77acc8..42502c01 100644 --- a/integration-tests/hardware/maestro/indicate-stress.yaml +++ b/integration-tests/hardware/maestro/indicate-stress.yaml @@ -1,21 +1,19 @@ -# Maestro flow: Indicate Stress Test +# Maestro flow: Indicate Stream Stress Test # -# This flow verifies that the library correctly handles a high-frequency -# stream of BLE indications over an extended period without dropping events, -# leaking memory, or crashing. +# 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 an indication on characteristic fff2 every -# 250 ms. This flow monitors the stream for 30 seconds and verifies that: -# - At least N indication values are received (N = 30s / 250ms * 0.8 = 96, -# allowing 20% slack for BLE scheduling jitter). -# - No error label appears during the test. -# - The app does not crash or freeze (Maestro can still interact with it). +# The BlePlxTest peripheral sends indications on the Indicate Stream +# characteristic (fff3) 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. -# - The test app is installed and foregrounded. -# - The peripheral firmware must implement the fff2 indicate characteristic -# sending a value every 250 ms when indications are enabled. +# - 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 on fff3 +# that sends indications at a regular cadence when enabled. # # Run: # maestro test integration-tests/hardware/maestro/indicate-stress.yaml @@ -25,120 +23,159 @@ appId: com.bleplxexample --- # --------------------------------------------------------------------------- -# 1. Launch and connect +# 1. Launch and scan # --------------------------------------------------------------------------- - launchApp: clearState: true -- assertVisible: "Start Scan" +- assertVisible: + id: "scan-start-btn" + timeout: 5000 + +- assertVisible: + text: "PoweredOn" + timeout: 10000 - tapOn: - id: "start-scan-button" + id: "scan-start-btn" - assertVisible: text: "BlePlxTest" timeout: 15000 +# --------------------------------------------------------------------------- +# 2. Connect to BlePlxTest +# --------------------------------------------------------------------------- - tapOn: text: "BlePlxTest" - assertVisible: - id: "connection-state-label" - text: "connected" + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- -# 2. Navigate to the indicate stress-test screen -# -# The test app must expose a button or navigation item to switch to the -# stress-test screen that monitors characteristic fff2 specifically. +# 3. Discover services and dismiss the alert +# --------------------------------------------------------------------------- +- tapOn: + id: "discover-btn" + +- assertVisible: + text: "Discovery" + timeout: 8000 +- tapOn: + text: "OK" + +# --------------------------------------------------------------------------- +# 4. Navigate to the Indicate Stream characteristic (fff3) # --------------------------------------------------------------------------- - tapOn: - id: "stress-test-button" + id: "char-fff3" + +- assertVisible: + 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: "indicate-count-label" - timeout: 3000 + id: "value-display" +- assertNotVisible: + text: "Monitor error" # --------------------------------------------------------------------------- -# 3. Run for 30 seconds +# 6. Run for 30 seconds with periodic liveness checks # # Maestro does not have a native timer loop, so we use a sequence of -# waitForAnimationToEnd calls as a coarse delay. Adjust the number of -# iterations based on actual animation timing in your app. -# -# Each assertVisible re-checks that the count is still updating and no -# error has appeared. +# waitForAnimationToEnd calls as coarse 5-second delays. Each checkpoint +# verifies the value-display is still updating and no error has appeared. # --------------------------------------------------------------------------- -# Check at t ≈ 5 s +# t ≈ 5 s - waitForAnimationToEnd: timeout: 5000 -- assertNotVisible: - id: "error-label" - assertVisible: - id: "indicate-count-label" + id: "value-display" +- assertNotVisible: + text: "Monitor error" -# Check at t ≈ 10 s +# t ≈ 10 s - waitForAnimationToEnd: timeout: 5000 -- assertNotVisible: - id: "error-label" - assertVisible: - id: "indicate-count-label" + id: "value-display" +- assertNotVisible: + text: "Monitor error" -# Check at t ≈ 15 s +# t ≈ 15 s - waitForAnimationToEnd: timeout: 5000 -- assertNotVisible: - id: "error-label" - assertVisible: - id: "indicate-count-label" + id: "value-display" +- assertNotVisible: + text: "Monitor error" -# Check at t ≈ 20 s +# t ≈ 20 s - waitForAnimationToEnd: timeout: 5000 -- assertNotVisible: - id: "error-label" - assertVisible: - id: "indicate-count-label" + id: "value-display" +- assertNotVisible: + text: "Monitor error" -# Check at t ≈ 25 s +# t ≈ 25 s - waitForAnimationToEnd: timeout: 5000 -- assertNotVisible: - id: "error-label" - assertVisible: - id: "indicate-count-label" + id: "value-display" +- assertNotVisible: + text: "Monitor error" -# Final check at t ≈ 30 s +# t ≈ 30 s — final check - waitForAnimationToEnd: timeout: 5000 -- assertNotVisible: - id: "error-label" - -# Verify the counter reached a reasonable minimum (96+ indications). -# The app should display the count as a number in indicate-count-label. -# Maestro cannot do numeric comparisons, so we check for the presence of -# a 2+ digit number by asserting a non-zero label is still visible. - assertVisible: - id: "indicate-count-label" + id: "value-display" +- assertNotVisible: + text: "Monitor error" # --------------------------------------------------------------------------- -# 4. Stop monitoring and disconnect cleanly +# 7. Disable monitoring # --------------------------------------------------------------------------- - tapOn: - id: "stop-monitor-button" + id: "monitor-toggle" + +- waitForAnimationToEnd: + timeout: 1000 # No error should appear after stopping - assertNotVisible: - id: "error-label" - timeout: 2000 + text: "Monitor error" -- tapOn: - id: "disconnect-button" +# --------------------------------------------------------------------------- +# 8. Navigate back and disconnect cleanly +# --------------------------------------------------------------------------- +- back - assertVisible: - id: "connection-state-label" - text: "disconnected" + id: "disconnect-btn" timeout: 5000 + +- tapOn: + id: "disconnect-btn" + +- assertVisible: + id: "scan-start-btn" + timeout: 8000 diff --git a/integration-tests/hardware/maestro/scan-pair-sync.yaml b/integration-tests/hardware/maestro/scan-pair-sync.yaml index 6143553c..ad531258 100644 --- a/integration-tests/hardware/maestro/scan-pair-sync.yaml +++ b/integration-tests/hardware/maestro/scan-pair-sync.yaml @@ -1,15 +1,15 @@ -# Maestro flow: Scan → Pair → Sync +# Maestro flow: Scan → Pair → Sync (full happy path) # -# This flow verifies the complete happy-path of discovering a BLE peripheral, -# connecting to it, performing a write, and receiving a characteristic -# notification. +# 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. -# - See integration-tests/hardware/test-app/README.md for app setup. # # Run: # maestro test integration-tests/hardware/maestro/scan-pair-sync.yaml @@ -24,74 +24,106 @@ appId: com.bleplxexample - launchApp: clearState: true -# Wait for the scan screen to be visible -- assertVisible: "Start Scan" +# Wait for the scan screen buttons to be visible +- assertVisible: + id: "scan-start-btn" + timeout: 5000 # --------------------------------------------------------------------------- -# 2. Start scanning for BLE devices +# 2. Wait for BLE to be powered on, then start scanning +# +# The BLE state is displayed as inline text: "BLE State: PoweredOn". +# We wait for it to appear before scanning. # --------------------------------------------------------------------------- +- assertVisible: + text: "PoweredOn" + timeout: 10000 + - tapOn: - id: "start-scan-button" + id: "scan-start-btn" -# The scan should start — wait for our peripheral to appear in the list. -# The peripheral advertises as "BlePlxTest". -- waitForAnimationToEnd +# --------------------------------------------------------------------------- +# 3. Wait for the BlePlxTest peripheral to appear in the device list +# --------------------------------------------------------------------------- +- assertVisible: + id: "device-list" - assertVisible: text: "BlePlxTest" - timeout: 15000 # Allow up to 15 s for the peripheral to appear + timeout: 15000 # --------------------------------------------------------------------------- -# 3. Connect to the peripheral +# 4. Tap the device row to connect +# +# The device row testID is device-item-{MAC/UUID}. Since the exact ID is +# unknown at authoring time, we tap by the displayed name instead. # --------------------------------------------------------------------------- - tapOn: text: "BlePlxTest" -# Wait for the connection state label to show "connected" +# --------------------------------------------------------------------------- +# 5. Wait for the DeviceScreen to load +# +# The DeviceScreen shows the discover-btn once the connection is established. +# --------------------------------------------------------------------------- - assertVisible: - id: "connection-state-label" - text: "connected" + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- -# 4. Discover services and verify the heart rate service UUID is shown +# 6. Discover services # --------------------------------------------------------------------------- -# The app should automatically discover services after connecting. -# Verify the expected service UUID is displayed. +- tapOn: + id: "discover-btn" + +# The app alerts on discovery completion. Dismiss the alert. - assertVisible: - text: "180D" - timeout: 5000 + text: "Discovery" + timeout: 8000 +- tapOn: + text: "OK" # --------------------------------------------------------------------------- -# 5. Write to the writable test characteristic (fff1) +# 7. Navigate to a characteristic +# +# If the service list is populated (future v4 API), tap the first visible +# characteristic. The testIDs follow the pattern char-{uuid}. +# For the read-counter characteristic on BlePlxTest firmware use the known UUID. # --------------------------------------------------------------------------- - tapOn: - id: "write-button" - -# A successful write should not show an error -- assertNotVisible: - id: "error-label" - timeout: 3000 + id: "char-fff1" # --------------------------------------------------------------------------- -# 6. Verify a notification is received from the heart rate characteristic +# 8. On CharacteristicScreen: read the value and verify it appears # --------------------------------------------------------------------------- -# The peripheral sends HR notifications at 1 Hz. -# Wait for the characteristic value label to be non-empty. - assertVisible: - id: "char-value" + id: "read-btn" timeout: 5000 -# The value should be a valid base64-encoded HR measurement (non-empty) -# Maestro does not support regex assertions, so we just check visibility. +- 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)". +- assertVisible: + id: "value-display" + timeout: 5000 +- assertNotVisible: + text: "Value: (none)" # --------------------------------------------------------------------------- -# 7. Disconnect +# 9. Navigate back and disconnect # --------------------------------------------------------------------------- -- tapOn: - id: "disconnect-button" +- back - assertVisible: - id: "connection-state-label" - text: "disconnected" + id: "disconnect-btn" timeout: 5000 + +- tapOn: + id: "disconnect-btn" + +# After disconnecting, navigation returns to ScanScreen +- assertVisible: + 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 00000000..a236bb2d --- /dev/null +++ b/integration-tests/hardware/maestro/write-read-roundtrip.yaml @@ -0,0 +1,121 @@ +# Maestro flow: Write → Read Roundtrip +# +# Verifies that a value written to the Write Echo characteristic (fff2 on +# BlePlxTest firmware) 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 on fff2 +# 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 + +- assertVisible: + id: "scan-start-btn" + timeout: 5000 + +- assertVisible: + text: "PoweredOn" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 2. Scan and connect to BlePlxTest +# --------------------------------------------------------------------------- +- tapOn: + id: "scan-start-btn" + +- assertVisible: + text: "BlePlxTest" + timeout: 15000 + +- tapOn: + text: "BlePlxTest" + +- assertVisible: + id: "discover-btn" + timeout: 10000 + +# --------------------------------------------------------------------------- +# 3. Discover services and dismiss the alert +# --------------------------------------------------------------------------- +- tapOn: + id: "discover-btn" + +- assertVisible: + text: "Discovery" + timeout: 8000 +- tapOn: + text: "OK" + +# --------------------------------------------------------------------------- +# 4. Navigate to the Write Echo characteristic (fff2) +# --------------------------------------------------------------------------- +- tapOn: + id: "char-fff2" + +- assertVisible: + 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" +- clearText +- inputText: "SGVsbG8=" + +- tapOn: + id: "write-btn" + +# Dismiss the "Value written successfully" alert +- assertVisible: + text: "Write" + 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 +- assertVisible: + id: "value-display" + timeout: 5000 +- assertVisible: + text: "SGVsbG8=" + +# --------------------------------------------------------------------------- +# 7. Navigate back and disconnect +# --------------------------------------------------------------------------- +- back + +- assertVisible: + id: "disconnect-btn" + timeout: 5000 + +- tapOn: + id: "disconnect-btn" + +- assertVisible: + id: "scan-start-btn" + timeout: 8000 From 28560db151205cba91a4ce24f5b5d33f36982df8 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 15:47:03 -0500 Subject: [PATCH 30/36] test: add Android JUnit5 and iOS XCTest native unit tests Add JUnit 5 + MockK tests for ScanManager throttle logic, ErrorConverter GATT mapping, PermissionHelper SDK gating, and EventSerializer serialization on Android; add XCTest suites for GATTOperationQueue serial/cancel/timeout behaviour, ErrorConverter CB/ATT error mapping, and EventSerializer snapshot dictionaries on iOS. Wire JUnit 5 platform into android/build.gradle. --- android/build.gradle | 8 + .../kotlin/com/bleplx/ErrorConverterTest.kt | 269 +++++++++++ .../kotlin/com/bleplx/EventSerializerTest.kt | 453 ++++++++++++++++++ .../kotlin/com/bleplx/PermissionHelperTest.kt | 202 ++++++++ .../test/kotlin/com/bleplx/ScanManagerTest.kt | 258 ++++++++++ ios/Tests/ErrorConverterTests.swift | 235 +++++++++ ios/Tests/EventSerializerTests.swift | 302 ++++++++++++ ios/Tests/GATTOperationQueueTests.swift | 236 +++++++++ 8 files changed, 1963 insertions(+) create mode 100644 android/src/test/kotlin/com/bleplx/ErrorConverterTest.kt create mode 100644 android/src/test/kotlin/com/bleplx/EventSerializerTest.kt create mode 100644 android/src/test/kotlin/com/bleplx/PermissionHelperTest.kt create mode 100644 android/src/test/kotlin/com/bleplx/ScanManagerTest.kt create mode 100644 ios/Tests/ErrorConverterTests.swift create mode 100644 ios/Tests/EventSerializerTests.swift create mode 100644 ios/Tests/GATTOperationQueueTests.swift diff --git a/android/build.gradle b/android/build.gradle index 6832d49d..92a136b6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -70,6 +70,10 @@ dependencies { 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' } react { @@ -77,3 +81,7 @@ react { libraryName = "BlePlx" codegenJavaPackageName = "com.bleplx" } + +tasks.withType(Test).configureEach { + useJUnitPlatform() +} 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 00000000..dcef0a67 --- /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 00000000..297cee49 --- /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 00000000..696cf8ad --- /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/ScanManagerTest.kt b/android/src/test/kotlin/com/bleplx/ScanManagerTest.kt new file mode 100644 index 00000000..5ea92938 --- /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/ios/Tests/ErrorConverterTests.swift b/ios/Tests/ErrorConverterTests.swift new file mode 100644 index 00000000..6f744779 --- /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 00000000..d46352d5 --- /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 00000000..d6336deb --- /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) + } + } +} From 17cd3e338e79efda59b416b7bc31837bfe216293 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 17:06:41 -0500 Subject: [PATCH 31/36] fix: register NativeBlePlx in TurboModule provider for iOS autolinking Add ios.modules config to codegenConfig in package.json so the codegen generates the NativeBlePlx -> BlePlx mapping in RCTModuleProviders.mm. Without this, TurboModuleRegistry.get('NativeBlePlx') returns null at runtime even though the native code compiles fine. Also exclude ios/Tests/ and ios/BlePlx.xcodeproj/ from the podspec source_files to prevent XCTest import failures in app builds. --- example/ios/Podfile.lock | 2 +- package.json | 7 +++++++ react-native-ble-plx.podspec | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 8f2a6521..2f5925ee 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2198,7 +2198,7 @@ SPEC CHECKSUMS: React-logger: b5521614afb8690420146dfc61a1447bb5d65419 React-Mapbuffer: f4ee8c62e0ef8359d139124f35a471518a172cd3 React-microtasksnativemodule: d1956f0eec54c619b63a379520fb4c618a55ccb9 - react-native-ble-plx: 3ade5fe23e99250c4107caf2b89c4a1ad7d37562 + react-native-ble-plx: 30ec90c1d8688ad65b395745b57087019eb7585f react-native-safe-area-context: ae7587b95fb580d1800c5b0b2a7bd48c2868e67a React-NativeModulesApple: 5ba0903927f6b8d335a091700e9fda143980f819 React-networking: 3a4b7f9ed2b2d1c0441beacb79674323a24bcca6 diff --git a/package.json b/package.json index c287d709..1f7cc454 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,13 @@ "jsSrcsDir": "src/specs", "android": { "javaPackageName": "com.bleplx" + }, + "ios": { + "modules": { + "NativeBlePlx": { + "className": "BlePlx" + } + } } }, "react-native-builder-bob": { diff --git a/react-native-ble-plx.podspec b/react-native-ble-plx.podspec index 90089307..dc6ca93d 100644 --- a/react-native-ble-plx.podspec +++ b/react-native-ble-plx.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/dotintent/react-native-ble-plx.git", :tag => "#{s.version}" } s.source_files = "ios/**/*.{h,m,mm,swift}" - s.exclude_files = "ios/BlePlx-Bridging-Header.h" + s.exclude_files = ["ios/BlePlx-Bridging-Header.h", "ios/Tests/**/*", "ios/BlePlx.xcodeproj/**/*"] s.swift_version = "5.9" s.frameworks = "CoreBluetooth" From 8201e8a5566065e0ddd72b29149e3e78c333076f Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 17:44:26 -0500 Subject: [PATCH 32/36] =?UTF-8?q?fix:=20turboModule=20runtime=20loading=20?= =?UTF-8?q?=E2=80=94=20codegen=20struct=20types,=20-ObjC=20linker=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ios/BlePlx.mm: change startDeviceScan/connectToDevice option params from NSDictionary* to Codegen C++ struct types, fix nullability annotations - example/ios/Podfile: add -ObjC to OTHER_LDFLAGS to prevent dead-stripping - example/src/App.tsx: restore real test screens --- .../BlePlxExample.xcodeproj/project.pbxproj | 6 +- example/ios/Podfile | 16 +++++ example/ios/Podfile.lock | 2 +- example/src/App.tsx | 62 +++++++++---------- ios/BlePlx.mm | 53 ++++++++++++---- 5 files changed, 93 insertions(+), 46 deletions(-) diff --git a/example/ios/BlePlxExample.xcodeproj/project.pbxproj b/example/ios/BlePlxExample.xcodeproj/project.pbxproj index 4aac5fdf..0d24bddc 100644 --- a/example/ios/BlePlxExample.xcodeproj/project.pbxproj +++ b/example/ios/BlePlxExample.xcodeproj/project.pbxproj @@ -260,6 +260,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2974F4A5QH; ENABLE_BITCODE = NO; INFOPLIST_FILE = BlePlxExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -273,7 +274,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-"; PRODUCT_NAME = BlePlxExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -290,6 +291,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2974F4A5QH; INFOPLIST_FILE = BlePlxExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -302,7 +304,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "com.iotashan.example.--PRODUCT-NAME-rfc1034identifier-"; PRODUCT_NAME = BlePlxExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Podfile b/example/ios/Podfile index fa3d9086..55dc373a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -30,5 +30,21 @@ target 'BlePlxExample' do :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 2f5925ee..061efaf4 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2237,6 +2237,6 @@ SPEC CHECKSUMS: RNScreens: 6cb648bdad8fe9bee9259fe144df95b6d1d5b707 Yoga: c0b3f2c7e8d3e327e450223a2414ca3fa296b9a2 -PODFILE CHECKSUM: b2563173982d3851c958f079c214a450847cc335 +PODFILE CHECKSUM: 7e0e156ac04ee33f77f15e2d84fa17c2ff0698ac COCOAPODS: 1.16.2 diff --git a/example/src/App.tsx b/example/src/App.tsx index 88b1bc09..9a8a7849 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,45 +1,45 @@ -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 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 ScanScreen from './screens/ScanScreen' +import DeviceScreen from './screens/DeviceScreen' +import CharacteristicScreen from './screens/CharacteristicScreen' export type RootStackParamList = { - Scan: { manager: BleManager }; - Device: { manager: BleManager; deviceId: string; deviceName: string | null }; + Scan: { manager: BleManager } + Device: { manager: BleManager; deviceId: string; deviceName: string | null } Characteristic: { - manager: BleManager; - deviceId: string; - serviceUuid: string; - characteristicUuid: string; + manager: BleManager + deviceId: string + serviceUuid: string + characteristicUuid: string properties: { - isReadable: boolean; - isWritableWithResponse: boolean; - isWritableWithoutResponse: boolean; - isNotifying: boolean; - isIndicatable: boolean; - }; - }; -}; + isReadable: boolean + isWritableWithResponse: boolean + isWritableWithoutResponse: boolean + isNotifying: boolean + isIndicatable: boolean + } + } +} -const Stack = createNativeStackNavigator(); +const Stack = createNativeStackNavigator() export default function App() { - const managerRef = useRef(new BleManager()); + const managerRef = useRef(new BleManager()) useEffect(() => { - const manager = managerRef.current; - manager.createClient().catch((e) => { - console.warn('Failed to create BLE client:', e); - }); + const manager = managerRef.current + manager.createClient().catch((e: any) => { + console.warn('Failed to create BLE client:', e) + }) return () => { - manager.destroyClient().catch(() => {}); - }; - }, []); + manager.destroyClient().catch(() => {}) + } + }, []) return ( @@ -62,5 +62,5 @@ export default function App() { /> - ); + ) } diff --git a/ios/BlePlx.mm b/ios/BlePlx.mm index 06b1fe6b..50967d9b 100644 --- a/ios/BlePlx.mm +++ b/ios/BlePlx.mm @@ -43,7 +43,7 @@ - (void)dealloc { // MARK: - Lifecycle -- (void)createClient:(NSString *)restoreStateIdentifier +- (void)createClient:(NSString * _Nullable)restoreStateIdentifier resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [_impl createClientWithRestoreStateIdentifier:restoreStateIdentifier resolve:resolve reject:reject]; @@ -63,9 +63,22 @@ - (void)state:(RCTPromiseResolveBlock)resolve // MARK: - Scanning -- (void)startDeviceScan:(NSArray *)uuids - options:(NSDictionary *)options { - [_impl startDeviceScanWithUuids:uuids options:options]; +- (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 @@ -76,10 +89,26 @@ - (void)stopDeviceScan:(RCTPromiseResolveBlock)resolve // MARK: - Connection - (void)connectToDevice:(NSString *)deviceId - options:(NSDictionary *)options + options:(JS::NativeBlePlx::SpecConnectToDeviceOptions &)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { - [_impl connectToDeviceWithDeviceId:deviceId options:options resolve:resolve reject: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 @@ -97,7 +126,7 @@ - (void)isDeviceConnected:(NSString *)deviceId // MARK: - Discovery - (void)discoverAllServicesAndCharacteristics:(NSString *)deviceId - transactionId:(NSString *)transactionId + transactionId:(NSString * _Nullable)transactionId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [_impl discoverAllServicesAndCharacteristicsWithDeviceId:deviceId transactionId:transactionId resolve:resolve reject:reject]; @@ -108,7 +137,7 @@ - (void)discoverAllServicesAndCharacteristics:(NSString *)deviceId - (void)readCharacteristic:(NSString *)deviceId serviceUuid:(NSString *)serviceUuid characteristicUuid:(NSString *)characteristicUuid - transactionId:(NSString *)transactionId + transactionId:(NSString * _Nullable)transactionId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [_impl readCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid transactionId:transactionId resolve:resolve reject:reject]; @@ -119,7 +148,7 @@ - (void)writeCharacteristic:(NSString *)deviceId characteristicUuid:(NSString *)characteristicUuid value:(NSString *)value withResponse:(BOOL)withResponse - transactionId:(NSString *)transactionId + 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]; @@ -130,8 +159,8 @@ - (void)writeCharacteristic:(NSString *)deviceId - (void)monitorCharacteristic:(NSString *)deviceId serviceUuid:(NSString *)serviceUuid characteristicUuid:(NSString *)characteristicUuid - subscriptionType:(NSString *)subscriptionType - transactionId:(NSString *)transactionId { + subscriptionType:(NSString * _Nullable)subscriptionType + transactionId:(NSString * _Nullable)transactionId { [_impl monitorCharacteristicWithDeviceId:deviceId serviceUuid:serviceUuid characteristicUuid:characteristicUuid subscriptionType:subscriptionType transactionId:transactionId]; } @@ -145,7 +174,7 @@ - (void)getMtu:(NSString *)deviceId - (void)requestMtu:(NSString *)deviceId mtu:(double)mtu - transactionId:(NSString *)transactionId + transactionId:(NSString * _Nullable)transactionId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [_impl requestMtuWithDeviceId:deviceId mtu:(NSInteger)mtu transactionId:transactionId resolve:resolve reject:reject]; From aa28ed6e0fbd244611b5d0fa7c77adf01662052b Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 17:54:41 -0500 Subject: [PATCH 33/36] fix: resolve TurboModule loading failure caused by dual react-native versions The BleManager constructor eagerly called TurboModuleRegistry.get() which used the library's devDependency react-native (0.77.0) instead of the example app's react-native (0.84.1). The older TurboModuleRegistry had different bridgeless checks that prevented the module from being found at runtime. Two fixes: - metro.config.js: add resolveRequest to force react/react-native to always resolve from the example app's node_modules, preventing version mismatches between JS and native runtime - BleManager.ts: lazy-initialize the native module via a getter instead of in the constructor, so the TurboModule lookup happens on first use rather than at instantiation time --- example/metro.config.js | 22 +++++++++++++++++++++- src/BleManager.ts | 10 ++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/example/metro.config.js b/example/metro.config.js index e3329633..f7154aef 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -2,6 +2,12 @@ const path = require('path'); const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); 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 @@ -14,9 +20,23 @@ const config = { resolver: { // Make sure Metro can resolve modules from the library root nodeModulesPaths: [ - path.resolve(__dirname, 'node_modules'), + exampleNodeModules, path.resolve(libraryRoot, 'node_modules'), ], + // 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); + }, }, }; diff --git a/src/BleManager.ts b/src/BleManager.ts index aa6bd96a..3bbfd0d9 100644 --- a/src/BleManager.ts +++ b/src/BleManager.ts @@ -227,7 +227,7 @@ function getNativeModule(): NativeBlePlxSpec { // --------------------------------------------------------------------------- export class BleManager { - private nativeModule: NativeBlePlxSpec + private cachedNativeModule: NativeBlePlxSpec | null = null private scanBatcher: EventBatcher | null = null private scanSubscription: EventSubscription | null = null private errorSubscription: EventSubscription | null = null @@ -238,10 +238,16 @@ export class BleManager { private scanBatchIntervalMs: number constructor(options?: BleManagerOptions) { - this.nativeModule = getNativeModule() this.scanBatchIntervalMs = options?.scanBatchIntervalMs ?? 100 } + private get nativeModule(): NativeBlePlxSpec { + if (!this.cachedNativeModule) { + this.cachedNativeModule = getNativeModule() + } + return this.cachedNativeModule + } + // ----------------------------------------------------------------------- // Lifecycle // ----------------------------------------------------------------------- From 2103ce6e387174ad93e846af02d2fe4384007f7e Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Sun, 15 Mar 2026 18:00:58 -0500 Subject: [PATCH 34/36] fix: remove library node_modules from metro resolution path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The library root had react-native@0.77.0 in devDependencies/node_modules, causing Metro to resolve the wrong TurboModuleRegistry for library code. Remove library's node_modules from nodeModulesPaths — only the example app's node_modules should be in the search path. --- example/metro.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/metro.config.js b/example/metro.config.js index f7154aef..525b2a11 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -19,9 +19,11 @@ const config = { watchFolders: [libraryRoot], resolver: { // 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, - path.resolve(libraryRoot, 'node_modules'), ], // Force react and react-native to always resolve from the example app's // node_modules. Without this, files under libraryRoot (e.g. src/) would From c9be6573e3ff46f256cc09050b32a5717725b0f8 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Mon, 16 Mar 2026 17:42:42 -0500 Subject: [PATCH 35/36] =?UTF-8?q?feat:=20v4=20TurboModule=20rewrite=20?= =?UTF-8?q?=E2=80=94=20docs,=20tests,=20Expo=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: Requires React Native >= 0.82.0 (New Architecture only). Removes enable()/disable(), setLogLevel(), and the old NativeModule bridge. Library: - TurboModule (Fabric) implementation replacing the old bridge - Android: Kotlin + Nordic BLE Library - iOS: Swift actors + CoreBluetooth - New monitorL2CAPChannel() API with iOS openL2CAPChannel fix - All 25 audit issues from v3 addressed - yarn prepack builds clean (68 files), lint passes, 32/32 tests pass E2E Tests (17 flows, both platforms): - Tests 01-11: Core BLE operations (scan, connect, read, write, notify, indicate, MTU, disconnect, UUID filter, background, L2CAP) - Tests 12-17: requestMTU, monitor unsubscribe, invalid write, L2CAP echo, payload boundaries, permission denied Example Apps: - Bare RN example (React Native 0.84, React Navigation) - Expo SDK 55 example (expo-router, verified on iPhone + Pixel 6) Documentation: - README: Expo-first quickstart - API reference, migration guide, troubleshooting, testing guide --- .eslintignore | 6 + CHANGELOG.md | 52 + README.md | 285 +- __tests__/BleManager.test.ts | 70 +- docs/API.md | 1001 +++++++ docs/GETTING_STARTED.md | 476 +++- docs/MIGRATION_V3_TO_V4.md | 282 ++ docs/TESTING.md | 278 ++ docs/TROUBLESHOOTING.md | 296 ++ example-expo/.gitignore | 8 + example-expo/App.tsx | 6 - example-expo/README.md | 35 + example-expo/android/.gitignore | 16 - example-expo/android/app/build.gradle | 172 -- example-expo/android/app/proguard-rules.pro | 14 - .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 32 - .../withintent/bleplxexample/MainActivity.kt | 61 - .../bleplxexample/MainApplication.kt | 55 - .../res/drawable-hdpi/splashscreen_image.png | Bin 59836 -> 0 bytes .../res/drawable-mdpi/splashscreen_image.png | Bin 59836 -> 0 bytes .../res/drawable-xhdpi/splashscreen_image.png | Bin 59836 -> 0 bytes .../drawable-xxhdpi/splashscreen_image.png | Bin 59836 -> 0 bytes .../drawable-xxxhdpi/splashscreen_image.png | Bin 59836 -> 0 bytes .../res/drawable/rn_edit_text_material.xml | 37 - .../src/main/res/drawable/splashscreen.xml | 3 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 8377 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 8031 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 10372 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 5199 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 5079 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 6526 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 11624 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 11145 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 14257 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 18266 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 18064 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 22474 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 25203 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 25030 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 30705 -> 0 bytes .../app/src/main/res/values-night/colors.xml | 1 - .../app/src/main/res/values/colors.xml | 6 - .../app/src/main/res/values/strings.xml | 5 - .../app/src/main/res/values/styles.xml | 17 - example-expo/android/build.gradle | 41 - example-expo/android/gradle.properties | 56 - .../android/gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes example-expo/android/settings.gradle | 18 - example-expo/app.json | 49 +- example-expo/app/_layout.tsx | 37 + example-expo/app/characteristic.tsx | 284 ++ example-expo/app/device.tsx | 330 +++ example-expo/app/index.tsx | 239 ++ example-expo/app/l2cap.tsx | 186 ++ example-expo/assets/adaptive-icon.png | Bin 17547 -> 0 bytes .../assets/expo.icon/Assets/expo-symbol 2.svg | 3 + example-expo/assets/expo.icon/Assets/grid.png | Bin 0 -> 53681 bytes example-expo/assets/expo.icon/icon.json | 40 + example-expo/assets/favicon.png | Bin 1466 -> 0 bytes example-expo/assets/icon.png | Bin 22380 -> 0 bytes .../assets/images/android-icon-background.png | Bin 0 -> 17549 bytes .../assets/images/android-icon-foreground.png | Bin 0 -> 78796 bytes .../assets/images/android-icon-monochrome.png | Bin 0 -> 4140 bytes .../assets/images/expo-badge-white.png | Bin 0 -> 4129 bytes example-expo/assets/images/expo-badge.png | Bin 0 -> 4137 bytes example-expo/assets/images/expo-logo.png | Bin 0 -> 3317 bytes example-expo/assets/images/favicon.png | Bin 0 -> 1129 bytes example-expo/assets/images/icon.png | Bin 0 -> 57176 bytes example-expo/assets/images/logo-glow.png | Bin 0 -> 331624 bytes example-expo/assets/images/react-logo.png | Bin 0 -> 6341 bytes example-expo/assets/images/react-logo@2x.png | Bin 0 -> 14225 bytes example-expo/assets/images/react-logo@3x.png | Bin 0 -> 21252 bytes example-expo/assets/images/splash-icon.png | Bin 0 -> 3317 bytes .../assets/images/tabIcons/explore.png | Bin 0 -> 215 bytes .../assets/images/tabIcons/explore@2x.png | Bin 0 -> 347 bytes .../assets/images/tabIcons/explore@3x.png | Bin 0 -> 468 bytes example-expo/assets/images/tabIcons/home.png | Bin 0 -> 253 bytes .../assets/images/tabIcons/home@2x.png | Bin 0 -> 343 bytes .../assets/images/tabIcons/home@3x.png | Bin 0 -> 479 bytes example-expo/assets/images/tutorial-web.png | Bin 0 -> 58959 bytes example-expo/assets/splash.png | Bin 47346 -> 0 bytes example-expo/babel.config.js | 6 - example-expo/ios/.gitignore | 30 - example-expo/ios/.xcode.env | 11 - example-expo/ios/Podfile | 58 - example-expo/ios/Podfile.lock | 1526 ---------- example-expo/ios/Podfile.properties.json | 4 - .../ios/exampleexpo.xcodeproj/project.pbxproj | 537 ---- .../xcschemes/exampleexpo.xcscheme | 88 - .../contents.xcworkspacedata | 10 - example-expo/ios/exampleexpo/AppDelegate.h | 7 - example-expo/ios/exampleexpo/AppDelegate.mm | 62 - .../App-Icon-1024x1024@1x.png | Bin 59468 -> 0 bytes .../AppIcon.appiconset/Contents.json | 14 - .../exampleexpo/Images.xcassets/Contents.json | 6 - .../SplashScreen.imageset/Contents.json | 21 - .../SplashScreen.imageset/image.png | Bin 59836 -> 0 bytes .../Contents.json | 21 - .../SplashScreenBackground.imageset/image.png | Bin 68 -> 0 bytes example-expo/ios/exampleexpo/Info.plist | 72 - .../ios/exampleexpo/PrivacyInfo.xcprivacy | 48 - .../ios/exampleexpo/SplashScreen.storyboard | 51 - .../ios/exampleexpo/Supporting/Expo.plist | 12 - .../exampleexpo/exampleexpo-Bridging-Header.h | 3 - .../ios/exampleexpo/exampleexpo.entitlements | 5 - example-expo/ios/exampleexpo/main.m | 10 - example-expo/ios/exampleexpo/noop-file.swift | 4 - example-expo/metro.config.js | 34 + example-expo/package.json | 59 +- example-expo/scripts/reset-project.js | 114 + example-expo/src/AppComponent.tsx | 17 - .../atoms/AppText/AppText.styled.tsx | 9 - .../src/components/atoms/AppText/AppText.tsx | 7 - .../AppTextInput/AppTextInput.styled.tsx | 15 - .../atoms/AppTextInput/AppTextInput.tsx | 7 - .../components/atoms/Button/Button.styled.tsx | 17 - .../src/components/atoms/Button/Button.tsx | 15 - .../ScreenDefaultContainer.styled.tsx | 9 - .../ScreenDefaultContainer.tsx | 10 - .../TestStateDisplay.styled.tsx | 22 - .../TestStateDisplay/TestStateDisplay.tsx | 29 - example-expo/src/components/atoms/index.ts | 5 - .../molecules/BleDevice/BleDevice.styled.tsx | 12 - .../molecules/BleDevice/BleDevice.tsx | 28 - .../DeviceProperty/DeviceProperty.styled.tsx | 16 - .../DeviceProperty/DeviceProperty.tsx | 16 - .../src/components/molecules/index.ts | 1 - example-expo/src/consts/nRFDeviceConsts.ts | 13 - .../components/commonScreenOptions.tsx | 21 - .../src/navigation/components/index.ts | 1 - example-expo/src/navigation/index.ts | 1 - example-expo/src/navigation/navigation.tsx | 31 - .../src/navigation/navigators/MainStack.tsx | 67 - .../src/navigation/navigators/index.ts | 1 - .../DashboardScreen.styled.tsx | 18 - .../DashboardScreen/DashboardScreen.tsx | 107 - .../DeviceConnectDisconnectTestScreen.tsx | 233 -- .../DeviceDetailsScreen.tsx | 19 - .../DeviceOnDisconnectTestScreen.tsx | 134 - .../DevicenRFTestScreen.tsx | 694 ----- .../InstanceDestroyScreen.tsx | 146 - .../MainStack/InstanceDestroyScreen/utils.ts | 131 - example-expo/src/screens/MainStack/index.ts | 6 - example-expo/src/screens/index.ts | 1 - .../src/services/BLEService/BLEService.ts | 456 --- example-expo/src/services/index.ts | 1 - example-expo/src/theme/colors.ts | 3 - example-expo/src/theme/sizes.ts | 4 - example-expo/src/theme/theme.ts | 10 - example-expo/src/types/TestStateType.ts | 1 - example-expo/src/types/index.ts | 1 - example-expo/src/utils/cloneDeep.ts | 1 - .../src/utils/getCurrentTimeAsBase64.ts | 15 - example-expo/src/utils/getDateAsBase64.ts | 14 - example-expo/src/utils/getDateUint8Array.ts | 13 - example-expo/src/utils/wait.ts | 1 - example-expo/tsconfig.json | 18 +- example/ios/BlePlxExample/Info.plist | 2 + example/src/App.tsx | 11 + example/src/screens/CharacteristicScreen.tsx | 87 +- example/src/screens/DeviceScreen.tsx | 277 +- example/src/screens/L2CAPScreen.tsx | 184 ++ example/src/screens/ScanScreen.tsx | 115 +- .../maestro/01-scan-connect-discover.yaml | 31 + .../hardware/maestro/02-read-counter.yaml | 36 + .../hardware/maestro/03-write-read-echo.yaml | 34 + .../hardware/maestro/04-notify-stream.yaml | 56 + .../hardware/maestro/05-indicate-stream.yaml | 54 + .../hardware/maestro/06-mtu-read.yaml | 30 + .../maestro/07-disconnect-reconnect.yaml | 61 + .../maestro/08-write-no-response.yaml | 35 + .../hardware/maestro/09-scan-uuid-filter.yaml | 65 + .../maestro/10-background-mode-ios.yaml | 57 + .../hardware/maestro/10-background-mode.yaml | 69 + .../hardware/maestro/11-l2cap-channel.yaml | 56 + .../hardware/maestro/12-request-mtu.yaml | 19 + .../maestro/13-monitor-unsubscribe.yaml | 36 + .../hardware/maestro/14-invalid-write.yaml | 29 + .../maestro/15-l2cap-echo-verify.yaml | 50 + .../maestro/16-payload-boundaries.yaml | 51 + .../maestro/17-permissions-denied.yaml | 36 + .../hardware/maestro/E2E-PLAN.md | 81 + integration-tests/hardware/maestro/README.md | 192 +- .../maestro/_connect-and-discover.yaml | 62 + .../hardware/maestro/disconnect-recovery.yaml | 56 +- .../hardware/maestro/indicate-stress.yaml | 79 +- integration-tests/hardware/maestro/run-e2e.sh | 248 ++ .../hardware/maestro/scan-pair-sync.yaml | 71 +- .../maestro/write-read-roundtrip.yaml | 79 +- ios/BLEActor.swift | 16 +- ios/BlePlx.mm | 24 +- ios/CentralManagerDelegate.swift | 30 + ios/PeripheralDelegate.swift | 34 + ios/PeripheralWrapper.swift | 15 +- package.json | 1 + src/BleManager.ts | 58 +- src/index.ts | 8 +- .../BlePlxTest.xcodeproj/project.pbxproj | 342 +++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../BlePlxTest/Assets.xcassets/Contents.json | 6 + .../BlePlxTest/BLEPeripheralManager.swift | 484 ++++ .../BlePlxTest/BlePlxTest/BlePlxTestApp.swift | 10 + .../BlePlxTest/BlePlxTest/ContentView.swift | 74 + .../BlePlxTest/BlePlxTest/Info.plist | 14 + test-peripheral-app/app/build.gradle.kts | 37 + .../app/src/main/AndroidManifest.xml | 27 + .../com/bleplx/testperipheral/MainActivity.kt | 533 ++++ .../app/src/main/res/layout/activity_main.xml | 38 + test-peripheral-app/build.gradle.kts | 4 + test-peripheral-app/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45457 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- .../android => test-peripheral-app}/gradlew | 14 +- test-peripheral-app/settings.gradle.kts | 18 + tsconfig.build.json | 9 +- yarn.lock | 2456 +---------------- 220 files changed, 7764 insertions(+), 8590 deletions(-) create mode 100644 docs/API.md create mode 100644 docs/MIGRATION_V3_TO_V4.md create mode 100644 docs/TESTING.md create mode 100644 docs/TROUBLESHOOTING.md delete mode 100644 example-expo/App.tsx create mode 100644 example-expo/README.md delete mode 100644 example-expo/android/.gitignore delete mode 100644 example-expo/android/app/build.gradle delete mode 100644 example-expo/android/app/proguard-rules.pro delete mode 100644 example-expo/android/app/src/debug/AndroidManifest.xml delete mode 100644 example-expo/android/app/src/main/AndroidManifest.xml delete mode 100644 example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainActivity.kt delete mode 100644 example-expo/android/app/src/main/java/com/withintent/bleplxexample/MainApplication.kt delete mode 100644 example-expo/android/app/src/main/res/drawable-hdpi/splashscreen_image.png delete mode 100644 example-expo/android/app/src/main/res/drawable-mdpi/splashscreen_image.png delete mode 100644 example-expo/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png delete mode 100644 example-expo/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png delete mode 100644 example-expo/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png delete mode 100644 example-expo/android/app/src/main/res/drawable/rn_edit_text_material.xml delete mode 100644 example-expo/android/app/src/main/res/drawable/splashscreen.xml delete mode 100644 example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 example-expo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 example-expo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 example-expo/android/app/src/main/res/values-night/colors.xml delete mode 100644 example-expo/android/app/src/main/res/values/colors.xml delete mode 100644 example-expo/android/app/src/main/res/values/strings.xml delete mode 100644 example-expo/android/app/src/main/res/values/styles.xml delete mode 100644 example-expo/android/build.gradle delete mode 100644 example-expo/android/gradle.properties delete mode 100644 example-expo/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 example-expo/android/settings.gradle create mode 100644 example-expo/app/_layout.tsx create mode 100644 example-expo/app/characteristic.tsx create mode 100644 example-expo/app/device.tsx create mode 100644 example-expo/app/index.tsx create mode 100644 example-expo/app/l2cap.tsx delete mode 100644 example-expo/assets/adaptive-icon.png create mode 100644 example-expo/assets/expo.icon/Assets/expo-symbol 2.svg create mode 100644 example-expo/assets/expo.icon/Assets/grid.png create mode 100644 example-expo/assets/expo.icon/icon.json delete mode 100644 example-expo/assets/favicon.png delete mode 100644 example-expo/assets/icon.png create mode 100644 example-expo/assets/images/android-icon-background.png create mode 100644 example-expo/assets/images/android-icon-foreground.png create mode 100644 example-expo/assets/images/android-icon-monochrome.png create mode 100644 example-expo/assets/images/expo-badge-white.png create mode 100644 example-expo/assets/images/expo-badge.png create mode 100644 example-expo/assets/images/expo-logo.png create mode 100644 example-expo/assets/images/favicon.png create mode 100644 example-expo/assets/images/icon.png create mode 100644 example-expo/assets/images/logo-glow.png create mode 100644 example-expo/assets/images/react-logo.png create mode 100644 example-expo/assets/images/react-logo@2x.png create mode 100644 example-expo/assets/images/react-logo@3x.png create mode 100644 example-expo/assets/images/splash-icon.png create mode 100644 example-expo/assets/images/tabIcons/explore.png create mode 100644 example-expo/assets/images/tabIcons/explore@2x.png create mode 100644 example-expo/assets/images/tabIcons/explore@3x.png create mode 100644 example-expo/assets/images/tabIcons/home.png create mode 100644 example-expo/assets/images/tabIcons/home@2x.png create mode 100644 example-expo/assets/images/tabIcons/home@3x.png create mode 100644 example-expo/assets/images/tutorial-web.png delete mode 100644 example-expo/assets/splash.png delete mode 100644 example-expo/babel.config.js delete mode 100644 example-expo/ios/.gitignore delete mode 100644 example-expo/ios/.xcode.env delete mode 100644 example-expo/ios/Podfile delete mode 100644 example-expo/ios/Podfile.lock delete mode 100644 example-expo/ios/Podfile.properties.json delete mode 100644 example-expo/ios/exampleexpo.xcodeproj/project.pbxproj delete mode 100644 example-expo/ios/exampleexpo.xcodeproj/xcshareddata/xcschemes/exampleexpo.xcscheme delete mode 100644 example-expo/ios/exampleexpo.xcworkspace/contents.xcworkspacedata delete mode 100644 example-expo/ios/exampleexpo/AppDelegate.h delete mode 100644 example-expo/ios/exampleexpo/AppDelegate.mm delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/Contents.json delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/Contents.json delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/SplashScreen.imageset/image.png delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/Contents.json delete mode 100644 example-expo/ios/exampleexpo/Images.xcassets/SplashScreenBackground.imageset/image.png delete mode 100644 example-expo/ios/exampleexpo/Info.plist delete mode 100644 example-expo/ios/exampleexpo/PrivacyInfo.xcprivacy delete mode 100644 example-expo/ios/exampleexpo/SplashScreen.storyboard delete mode 100644 example-expo/ios/exampleexpo/Supporting/Expo.plist delete mode 100644 example-expo/ios/exampleexpo/exampleexpo-Bridging-Header.h delete mode 100644 example-expo/ios/exampleexpo/exampleexpo.entitlements delete mode 100644 example-expo/ios/exampleexpo/main.m delete mode 100644 example-expo/ios/exampleexpo/noop-file.swift create mode 100644 example-expo/metro.config.js create mode 100755 example-expo/scripts/reset-project.js delete mode 100644 example-expo/src/AppComponent.tsx delete mode 100644 example-expo/src/components/atoms/AppText/AppText.styled.tsx delete mode 100644 example-expo/src/components/atoms/AppText/AppText.tsx delete mode 100644 example-expo/src/components/atoms/AppTextInput/AppTextInput.styled.tsx delete mode 100644 example-expo/src/components/atoms/AppTextInput/AppTextInput.tsx delete mode 100644 example-expo/src/components/atoms/Button/Button.styled.tsx delete mode 100644 example-expo/src/components/atoms/Button/Button.tsx delete mode 100644 example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.styled.tsx delete mode 100644 example-expo/src/components/atoms/ScreenDefaultContainer/ScreenDefaultContainer.tsx delete mode 100644 example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.styled.tsx delete mode 100644 example-expo/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx delete mode 100644 example-expo/src/components/atoms/index.ts delete mode 100644 example-expo/src/components/molecules/BleDevice/BleDevice.styled.tsx delete mode 100644 example-expo/src/components/molecules/BleDevice/BleDevice.tsx delete mode 100644 example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.styled.tsx delete mode 100644 example-expo/src/components/molecules/BleDevice/DeviceProperty/DeviceProperty.tsx delete mode 100644 example-expo/src/components/molecules/index.ts delete mode 100644 example-expo/src/consts/nRFDeviceConsts.ts delete mode 100644 example-expo/src/navigation/components/commonScreenOptions.tsx delete mode 100644 example-expo/src/navigation/components/index.ts delete mode 100644 example-expo/src/navigation/index.ts delete mode 100644 example-expo/src/navigation/navigation.tsx delete mode 100644 example-expo/src/navigation/navigators/MainStack.tsx delete mode 100644 example-expo/src/navigation/navigators/index.ts delete mode 100644 example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.styled.tsx delete mode 100644 example-expo/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx delete mode 100644 example-expo/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx delete mode 100644 example-expo/src/screens/MainStack/DeviceDetailsScreen/DeviceDetailsScreen.tsx delete mode 100644 example-expo/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx delete mode 100644 example-expo/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx delete mode 100644 example-expo/src/screens/MainStack/InstanceDestroyScreen/InstanceDestroyScreen.tsx delete mode 100644 example-expo/src/screens/MainStack/InstanceDestroyScreen/utils.ts delete mode 100644 example-expo/src/screens/MainStack/index.ts delete mode 100644 example-expo/src/screens/index.ts delete mode 100644 example-expo/src/services/BLEService/BLEService.ts delete mode 100644 example-expo/src/services/index.ts delete mode 100644 example-expo/src/theme/colors.ts delete mode 100644 example-expo/src/theme/sizes.ts delete mode 100644 example-expo/src/theme/theme.ts delete mode 100644 example-expo/src/types/TestStateType.ts delete mode 100644 example-expo/src/types/index.ts delete mode 100644 example-expo/src/utils/cloneDeep.ts delete mode 100644 example-expo/src/utils/getCurrentTimeAsBase64.ts delete mode 100644 example-expo/src/utils/getDateAsBase64.ts delete mode 100644 example-expo/src/utils/getDateUint8Array.ts delete mode 100644 example-expo/src/utils/wait.ts create mode 100644 example/src/screens/L2CAPScreen.tsx create mode 100644 integration-tests/hardware/maestro/01-scan-connect-discover.yaml create mode 100644 integration-tests/hardware/maestro/02-read-counter.yaml create mode 100644 integration-tests/hardware/maestro/03-write-read-echo.yaml create mode 100644 integration-tests/hardware/maestro/04-notify-stream.yaml create mode 100644 integration-tests/hardware/maestro/05-indicate-stream.yaml create mode 100644 integration-tests/hardware/maestro/06-mtu-read.yaml create mode 100644 integration-tests/hardware/maestro/07-disconnect-reconnect.yaml create mode 100644 integration-tests/hardware/maestro/08-write-no-response.yaml create mode 100644 integration-tests/hardware/maestro/09-scan-uuid-filter.yaml create mode 100644 integration-tests/hardware/maestro/10-background-mode-ios.yaml create mode 100644 integration-tests/hardware/maestro/10-background-mode.yaml create mode 100644 integration-tests/hardware/maestro/11-l2cap-channel.yaml create mode 100644 integration-tests/hardware/maestro/12-request-mtu.yaml create mode 100644 integration-tests/hardware/maestro/13-monitor-unsubscribe.yaml create mode 100644 integration-tests/hardware/maestro/14-invalid-write.yaml create mode 100644 integration-tests/hardware/maestro/15-l2cap-echo-verify.yaml create mode 100644 integration-tests/hardware/maestro/16-payload-boundaries.yaml create mode 100644 integration-tests/hardware/maestro/17-permissions-denied.yaml create mode 100644 integration-tests/hardware/maestro/E2E-PLAN.md create mode 100644 integration-tests/hardware/maestro/_connect-and-discover.yaml create mode 100755 integration-tests/hardware/maestro/run-e2e.sh create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest.xcodeproj/project.pbxproj create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/Assets.xcassets/Contents.json create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/BLEPeripheralManager.swift create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/BlePlxTestApp.swift create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/ContentView.swift create mode 100644 test-peripheral-app-ios/BlePlxTest/BlePlxTest/Info.plist create mode 100644 test-peripheral-app/app/build.gradle.kts create mode 100644 test-peripheral-app/app/src/main/AndroidManifest.xml create mode 100644 test-peripheral-app/app/src/main/java/com/bleplx/testperipheral/MainActivity.kt create mode 100644 test-peripheral-app/app/src/main/res/layout/activity_main.xml create mode 100644 test-peripheral-app/build.gradle.kts create mode 100644 test-peripheral-app/gradle.properties create mode 100644 test-peripheral-app/gradle/wrapper/gradle-wrapper.jar rename {example-expo/android => test-peripheral-app}/gradle/wrapper/gradle-wrapper.properties (74%) rename {example-expo/android => test-peripheral-app}/gradlew (94%) create mode 100644 test-peripheral-app/settings.gradle.kts diff --git a/.eslintignore b/.eslintignore index 68b1c204..810736b0 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/CHANGELOG.md b/CHANGELOG.md index af62d095..573b2e66 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 57cecaed..14490533 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__/BleManager.test.ts b/__tests__/BleManager.test.ts index dd49f6b5..e7146596 100644 --- a/__tests__/BleManager.test.ts +++ b/__tests__/BleManager.test.ts @@ -11,6 +11,8 @@ let stateChangeHandler: ((event: any) => void) | null = null let restoreStateHandler: ((event: any) => void) | null = null let bondStateChangeHandler: ((event: any) => void) | null = null let connectionEventHandler: ((event: any) => void) | null = null +let l2capDataHandler: ((event: any) => void) | null = null +let l2capCloseHandler: ((event: any) => void) | null = null // Convenience aliases for single-handler tests const getScanResultHandler = () => scanResultHandlers[scanResultHandlers.length - 1] ?? null @@ -158,6 +160,22 @@ const mockNativeModule = { 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 + }) + } }) } @@ -226,7 +244,7 @@ describe('BleManager', () => { if (device) received.push(device) }) - expect(mockNativeModule.startDeviceScan).toHaveBeenCalledWith(null, null) + expect(mockNativeModule.startDeviceScan).toHaveBeenCalledWith(null, {}) expect(mockNativeModule.onScanResult).toHaveBeenCalled() expect(getScanResultHandler()).not.toBeNull() @@ -686,4 +704,54 @@ describe('BleManager', () => { 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/docs/API.md b/docs/API.md new file mode 100644 index 00000000..9f128635 --- /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 2d5e4277..24415b7b 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 00000000..31c2e92c --- /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 00000000..7f174f25 --- /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 00000000..935f1497 --- /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/example-expo/.gitignore b/example-expo/.gitignore index 05647d55..f8c6c2e8 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 f0866fa2..00000000 --- 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 00000000..8d8053f8 --- /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 8a6be077..00000000 --- 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 5aa3e210..00000000 --- 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 551eb41d..00000000 --- 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 3ec2507b..00000000 --- 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 b1ef6922..00000000 --- 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 a5f087dd..00000000 --- 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 cf295b0b..00000000 --- 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 c52c2c68019b49c56da4faf7d8835a8392cfef7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59836 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 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 c52c2c68019b49c56da4faf7d8835a8392cfef7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59836 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 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 c52c2c68019b49c56da4faf7d8835a8392cfef7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59836 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 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 c52c2c68019b49c56da4faf7d8835a8392cfef7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59836 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 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 c52c2c68019b49c56da4faf7d8835a8392cfef7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59836 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 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 5c25e728..00000000 --- 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 c8568e16..00000000 --- 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 3941bea9..00000000 --- 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 3941bea9..00000000 --- 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 0a61c1b272eea7a762c3657ede11f4ce39a8cab0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8377 zcmch7dpy(q`+xV{i93`m=F~wEH95qhmJSYO4rMhNmcvZCw>ea5icYuYOi2@J6|L^b zFy>gq+Qjlf%4kZn7`Mu)Ldfr0_xJbsJ$~QEvdhv=kt1A5)QiW zQ~g8dj|B@Bs5^V&T?Q?zHX$Vy=Q~jsE9Y zBGTW(+0~peOlDkgasYqAceOoHES&I_UP{P#kY7e;iC`#fV(xRe@|XT1yR>^8Rd0&M zqS{fV@h5AQqE~0*Od@F;;Nb0_H=GHci!QY)Rw0P6WzUBRB;wq;9lJ{^Di$gZo&|>$ zRC7#3iMhtcqQw0Cw(19*l=kZMT;oUi%`dXaCRB+qg6b<4L{KwupJH-a-p~6d2k@Hr zr)OBTcokYE=HBI~&R-f0i@5d2{3YDw;cBZK)^T3;tI&!Nv~Il8x%oRStMT{}Unf*% zLPD-r8E1OEh#SJwlT~j?C6OddFJ=mEFaG#-a@ExNq-t~s(F4yj5yAUkOyIbvjiM7- zEljEx(?BU&gF#c~FOZZ^KP;V&%O8G=$}DkxxGdLLh~#HKZV#;(oqKSc=lzO`4%gEq z$ad{dKkPL;Su+cB6C#_rfiUNe*U!hWuTBY~Ycr;!6J8kZ`OqqjzZO(<8 z+{Z72EN?Ugxf49$mAM_t@V?$Kl0=izuycjFued%^fBWs-ULD>Vj><`n&rfLo`YiQS z(pKatZM7L;IZe9e`M{c7lnbY*(beeWBMLuz(VnFGyZew!jM;P)lidE9ajIrrB(c<2 z=xTH**Z77=WFZ!73{CFPTfO>&joSPobgylpHJl0!HG0k4Se5XhB#(%2Oa=G91)Yvj_@u}^1p{Hl_fhNFh-?Y@9et3=1}-&8U}V8WU>`!oVg8L6s)fxQXl0Ku_oE#FrLhP zd{q&y>G}DOC@G!RWI{Sw8%yXaZ-~(I6|>t%lW9I&A(6lgwE5(LH;i~Y=1}c z{BFHQbfM>==sL$0Q*>5R{OB3$95;9O)Q@vBtwzqC*%3<68yFK))PvL&iR-?l$!AC0Z6k0hR-9pl})<9Bm9+N5so zd;61^bDKV&PLekdWF^WnN=pyCjx^;5q+P$ho**l-6c+nB_4K$V$PQ0cIPnct7HTY_ zL3$dquUh}y=$wuyq%^c?{oEDMf&_@UKa^;UeR)FLh1u^*OsT5YNgAoJwHef4*cmkV z1rQR~oVQVzlq1uQjn9nSq0f9@RomNV%Z*~BZ|MK@Nh6YYl5X>1FeEm9^azsVg)-P; z7WRUBOSQ3NXtL((S^bU>`tM3-)*5&*VI>z`GHYy%gHR4Gy#namXR|Iks~DA9W&2s&J=}I_+Yl7TVMWpBjWVw(0{6#eEucd?K(|57aX%W zQ{(-K8=SiVat21j*9IVw?2H8s3nA{K#sZ&9rI!d>GEcn^|!Q@(Kh5bW~<`dcz--fN7)5nW0Je7a&_doi?W(6BjvCcqG zKc(%h`RW7xb2BG@4%fX$HkMpD)-aIu_wd{3snM)lW7yu7rlxz3BYc3g8q@q>_cL&M z`l_}zNF+g~oS<~ueK+z4>|)iEC(?mn0}sRmfB8m8#!!0)8(Z7^`J1-hG3)2YoqYdv zrXH~D*zPe`Kmq6U$M?Ib5QQXyto%WTZQaiwER<+$P7{)k*&Y)AxMaOUYP)*#h2> zbKuUFzbX9o_TNU{&s~&PecIG|GHUY6($rKnIQ{T|mf{}kP(Bw?h9)m;&6lw-Yd!sQx7dP>BS+g9LBf6P7zQBg!L*hvTC)`?#nZW z5$p`I;MDeQJ?q7hgQtFHio5`4)8)2>o*fxv6BxAEaYb2>U7F3Vd8|hU!12$-{8u>q zs$Z|8iny`+?q*nC^kNEV+N7JV!A`^3q1&vU9^JPfV41og(BdI#GvxZdeM7T@{y)b>l^E--D389c4ggHl zg&cwHEvMzIxiDK#XKlosWXYS-cK!6HO3)sNlaY~p8f+QBG=rCvw7IPF(UG;{lYoNeIXKHcS_ zs;aUeu-B~lqvEQ(YWnNYX5GK`Xo+yqVS0@Z-i2y++Qt09vg8aO8#}uUYoM|Wa?=)g z5Qry|xE1+z6q8AN`4&PQbWVGS|MuG#M{bN%1X=0atmY^ z5Ice~EG$v??9(lr;bJ$I-1lgzy1M&;0}dwYwI{I&w^*x1I+>ZI@hASe5K)smUh@Jh zvy^uTgX3OD(u}U#Qci@oZM!(CDnxGC!m$vB({1i|f2HsDrlfDMKA!E&BtJlHew3eG z-HFn4fzU~DviB}z0K5beG?%lxspdMWmDM^YBO(Mj+a6L+hMSvTZ}K&|o~4}vav)Ye zpwu2hxS4C9^wvs;RqU94AEl`f6FRCT2)?gDjYeJS4B1FQ6V zuS(p`u{pFsLyT?;1He-=Dc;6?bPnD7Duljr5iQsFTEFE3iE8&M^p2qI#9|BN*UA(K zU$olt<;5sLOj7R6BEK-#Y|bLUr`ztjyf(VHK(En+zI}sWA*GNTou=(IhN9B&8%c0wXj&yyB&(Dv~MT&5- z_gc}%s|`{Q`HJuLj_B;J+~`fQyHFd1_9-?>VKjzV-R)2=K?&C2Z#bE_U|RVTn9I-P z9~P8x2q>nn-vLmS=jZ1%=FZ3cqJX|y;Et1#)=XopbH}Pe&+B2J%0xH^4_7?CtGs2) z!B6r1mO>;z@ZF5SoSYmJ-K9%oGFD1iKrTPO6-~YjUPhHz79G`}m}m_HCW2HbN4d0* zoB`oc@#Jh|GEEY^oj3}p4PukO(*xU7zvoU0j3Y5-1IQ#>pfW9L_Li(?in@w58NW#m z<3T?+P4{>0PfrAH1h&+8MaOq=D zZEtazw;M_nm@^&Se7!N@W+Aee9Jm} z;zBD(UOBCqR1m#|o*IP|P$tB?UB5Y}bEPw;d`&zXG=qo>^PQB6GF^UE8=!?&sIN|p z4?l6jVa3$PsCs#8W>4JO5E20>`O&5pbZxKIW6(vgQx~m>+MV8o4%dc5~ zF-{D8(6JcM>qt>*O8jV>L#*O}<`azeS>b4L#*nh-LymR&26B#Pf#GBJaiLSjKx4Bgy&6qtQ5<5!EQH10WMf14r7~}f9_*!?vLs$9&CLOsvV@x4 zxRtLT#iC@abJrmR`%!)(=;zsl+`m=U zCJA2Iq^_TJBNG6q;CfvF%4OkxkHZizN*8kTH#KtsXhx5{vLdZ_I|9jHYv_zyxSvUe z!=L*prg%2gE|?{PT`cL4^8BgOMPjK1z^*r?V>mK)7YKf1Y)D1UtKrBgX09>l4+yiT zN8@&BIF`UH;pW!l#zRn_x^G0ZPE6ILZ&>UJ@br@dThQ0{hnNRxK6=cvK&6lZk#qs* zA-HrgVAjzheg*O`SEH@}HC!{d2jOS}12^8K2VxV7J;e+f%0;{LpXvdQUZch1RgQu~ zn-gJ!BUH{D7Q;%Cwafpev@jlzJx3gP$wi^dIw3{Ni#_qH&b$esH+VD27#>R01raPw zg%yCE3RqT1K|@)n*g={P70RYA&>iT++>aa}sQ}Z|Lo9E-jnp!4RlfcWvVbA$q8`HE z2xuvXSm;ZzO>J?4Dp*#{2{3J4_9g#aXaa68!Z3t5akz|=kc(i)o~|+M@nC6DD>;;| zT-0W?UgLn`Rm0n<2*)OLIW2+a1F;Dov;`%C>iv+|I!TcJkn4qdG@QM56q2dOktjwu z!fisBY|^h}$b9&K9+)b-n{ics1xcFHRKEv0)q;T%sh+XY`yR^!F(5>)&aqIa0$qN< zfl3INtrROARf5bX8Nz5CRymb4@frcO@|EH0kV5b^Xm39oLH;Y9`8Y!ZZaC{ za#}Ql2=1lb&n6{3`1HrIywf#JXfoj*PZ&wez(Ja*0qao#MJ3=$q}aGuZB~~Gk`@Mh z?(1vLAd~4EpiE0jqVV62jjc0iAPOQH0EfDQ0bfGUJNaq=QJw|$PT3q4pE8MQ2JU&p zuVz%P7J<~};h1tEx@K9OL-hzm?sfH62B6TSe(mCHR4 zhs2Cu08Hz%S=IUWvu4fil7$d@Og2yvp53ib2RBF z35qGOq!=`xftH*@B!cE-05CT=7XSe;F)&8Dsm03>J9ayO6nU)Ps4M|l%4BMbv#DjQm$md;iUl~q(jV}qE*$SlJ zVKZ3jMiiqm)eZI{e|-8QM?XM<-t(bU;dNPnX0L$3Pu1MZYR$A<-D-o;JK+cE_@+Pu zYy65X!|Ac~yg^h#cE#9f0(gplcd}iUqbF-P|pE_rAD8W za`baE((CUwLOrr=n}+SqoxwSIdDpU9*FWf}`g(R97?=kEPgE8v^DkNlm^$Oqg9IT~ z{%%uY4GWWUV~0+qk_n2kAiG%5(&FHyt8x}pSEljlm;P;Au?ndQI0J?zxWR}{5VE}uqKXYMtLr z9b}S-!?ap+%e@?WJn{IDLLlMQe--l3n9J21Fb5%Pi@+82CA!^g&HSBVuA<@tZ{G7k zHp6HN5()&?pHBnbg5>&gy_5Hvr(VdDt_JglT7Go{p#*B7aAG6Eah*4X4+(^VJ%LX! zAH<1yQnR*dO_B;+SYJFd#~D@9Oj0Veo;!bJD#=$2?uwa%$DaysG?G|rm>38fYxAii zbek<$4e5`=_2N1@w4myn1hJ7GrLi*Ilux+brW+x zh%z@Xd@(en{w{RiR1#zr9`6iKt-jQ!J$QPD!Vm2n|2cOoFS3R9ZH5TO&Tow+5H;f> zvylW@EOAxiFBqhjBN|H-j?0g^Efws@mYK;rDV^R7{_1z3adL2hK;~zkt~u^m;QF6d zvzIy*{WGTLe+(6Zosnxi^|>MqY{Y~3RW+cR;JV@j!qFM|0|3!SHe(tpLh`4luRzQl zugDpgnra($?ZRkaO`_(0yq^6rd~RTZ-Bm7VYEn07pec%z_qQx>{|E3O*fI$Y3yPLWsa6Aj|=&f>L}eg`@oQXp!PkU{y+kpMSD+e4J5G zPy#mp%=g1^vNfe;>+8@8g-Z(68rT3yoz@#S#>FdLa4hZq0AHL=u7W9p>V+`2ykq8p zg#zpHvxo0Op^7(VPi_9E+DLRFeS=}*%03$i`_AWF#M7gh(&8{3)dN$y$rix<0y@Ug zL?Eg7<&Ew`w`Pn~&?mpZ`{WAW1CJm0`qc||;D|MFa`g0NEi)h{_0sNi8+RaUrs$@s zdi{j&ueJtN;$(8AGaJ*|(xM71gvUc5cpyGTfx4J?Fn&R;;(30M>94*!YU2eC~!8O^a83$5nNMK z2cfF4h0Z3pb^z-Q-lg4ZRbr~&srBmSj9kSvNTn-aW|ftezz!lZFdQ^|;0tRW8)N_N zKP6iL%Rt6K!w}r*o!sfvZ)f!z+A@Kx;NyiduuPMW7K!Tj^ny|;wg(gf%FS}uLXr+* zktvK{Z6)ZNO&#rymV!~<@L28VCE^=Lv|dv$D#bvAMOalhHTt_ zb)SAUwB}c&%0|cgmw_w%l8(9F6ch{&l`*y$<%nDY>kIOSi&~AtABPgWDjC`aWt{GC zJ&DQ^qPEHppx<9DHSlt6w{Q0cwt~i)qUi_Ko_UVx3U%*7Bd`|=JzXH>Qc3F#X7&GD zv+s%pLscCD7!WdGFfSv}Z7Qy0Iu3_*{?$tZGe^U64z_jH*+fKZrip zRqj^T3tkW;&znu_opG;Dkv$81;TgBB&WeVTSB|?}(U!z2r!e-FcHe#TXWOrC=?k56 z^J&mvU26bx7e0*A;7t@mC@bRG5txujf{GkEqsP%XPA2LiTvvIDtgDifB)4 zo)%`{=&U$-)T~+h1`}s)pp}l&xeGb}U%$Hxng_^n%tH<(A@^n+lWdD;|2mK_CKJGW zxU$p}s_jjta93M1cV(fq;`UD%Gr9_%{fJ`E(!!MGzX~aYm{jzzBZ)5mp23N&!Rcx% z>xAYp7(k@rIKEvPxQG8ke_ro<)W7|C3&jzEnfL7$H1A0VKYjkZK=A%Olu!RF2N&lT ap01I+?%QjQg+EDH;Oyw`z}f46`Tqd3=8|dv 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 ac03dbf69f0d7bfe4cd103b20ae5228aec2ce383..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8031 zcmb_hdpy(s_utLtGHkg`Zc8bZ%S=M9tGN^@a*HrwN^+^(O&b#NVN%ScMp8^^$St?I zqzO?{?pA~(_fM|zd;9+W`Tqa=J@(l4cyF)Ud7anmJfF{V63$zh2@CEO1c5-pr_G5r z!1t5gH$NQsY+jO-0f7__o+c7(L&3kBgmU_V9xW_(uZD=+bgvG53vq64Qku$vZfzg1 zf12)Cl2P~K`xhsMz2nu)68jWq{UQ4|KG%MJ>iQk`WjhWIfpF~2ZOx&?bLt2_S5 zZo|c2l!e5Z5$~DW8tGuj(l)5oi|hm=26+V*NQ%2{7;RC0UcM4h$T4 zG!@rU_ue(HS=nb>W7qU4vyW-T(g*+BHtv1anF>cyyaYo8FELA^|D`Ve{w-$)x?7a~ z3OvXOW|X?bsv2u@?dNY^fBHw+Vy4krmE1H*jS%8SlGJd}E4*{DJI~9@6EkE9>V(*g zYu2hp;7WN%p~|+TAj0`V`bL_{VOtAC*1&1SITy}J9%$PFSqHL*$RjGL*P&I=?_V}H z7H?AX2L}iJVA7(C+dL6?HoOz0D|-df4xOT6o0nMyedOTh@mrraw{F(G_X>MxYXOzpQ~!3erfmnX)R}f_bRll5anb0!>IwQ)?9HZRi0)pU zJrllYU70Hmsyni?2Lp=h{AYM}${oc?K34@7-04b+migU6BqN+hLHx)(#T^mTx_J`0 z1zv*&APkz>`WIVAMQ)r3I1;dzdzIvDYoVRh{xt@>s`5(lqN|dvcAew|o-Ra%=R*G6 z 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 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 26deb9725b93d28d1abab5f2ec9fa854347b7a24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10372 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 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 fd1a2c712acd1eaf5ffc3ba1ec9edcdc4a96197c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5199 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 ULJ*w>3_FmIhrfIM_Q;I?0&7K2ZU6uP 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 4d3ac7a4e63d1954fbf72d3bc9633ab895613e00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6526 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 Q|0*m59V6{3%^MH@2XeGXI|C&+GYoUe^^HYYQ>q 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 T!gB&9sDHtkS)0}z^Sb= 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 Za-wxV!t(TUd!WS@wBOc+(qO|#{9nh>`J4a% 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 95ac5824e9d715c20b2a4330889120c60bbfcd87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14257 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 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 f7f1d06908af8ba1320d9aa6474f792883db430a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18064 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 KpUXO@geCyPqIRYL 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 6508f74207cc3a45391a22e58c598a273d863c3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22474 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 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 d54716bfad547896ba03ea9230518d615477fba2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30705 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 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 3c05de5b..00000000 --- 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 f387b901..00000000 --- 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 8d4b90da..00000000 --- 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 f03e23f8..00000000 --- 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 932bf7b3..00000000 --- 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 41b173c4..00000000 --- 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 d64cd4917707c1f8861d8cb53dd15194d4248596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43462 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 diff --git a/example-expo/android/settings.gradle b/example-expo/android/settings.gradle deleted file mode 100644 index 316ece7e..00000000 --- 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 a94ff001..e41acec3 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 00000000..f0351a04 --- /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 00000000..6c117926 --- /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 00000000..2c56d81a --- /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 00000000..6525ee17 --- /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 00000000..d0bedd75 --- /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 03d6f6b6c6727954aec1d8206222769afd178d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17547 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 + + 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 0000000000000000000000000000000000000000..eefea24246267f9a2446460c389a483577a3adb5 GIT binary patch literal 53681 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 literal 0 HcmV?d00001 diff --git a/example-expo/assets/expo.icon/icon.json b/example-expo/assets/expo.icon/icon.json new file mode 100644 index 00000000..7a2c33cd --- /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 e75f697b1801871ad8cd9309b05e8ffe8c6b6d01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1466 zcmV;r1x5OaP)F>1w{Y zBeHf{*q3<2*AtQf4s&-m0MsH$EBv51Nj=s=Appw|nd1Yi(-DKZBN$9bAlWN83A_)0 z$4U=S!XyBuAm(`t#aW=l*tHPgHRE~MrmzGWN*Eidc=$BV2uYe|Rpi@t-me&ht6I?| ze$M(9=%DxSVTwNL7B*O`z`fRE$T)18O{B^J5OHo#W%kD-}gAcJO3n1x6Q{X*TFh-d!yx?Z$G16f%*K?exQ+p ztyb%4*R_Y=)qQBLG-9hc_A|ub$th|8Sk1bi@fFe$DwUpU57nc*-z8<&dM#e3a2hB! z16wLhz7o)!MC8}$7Jv9c-X$w^Xr(M9+`Py)~O3rGmgbvjOzXjGl>h9lp*QEn%coj{`wU^_3U|=B`xxU;X3K1L?JT?0?+@K!|MWVr zmC=;rjX@CoW3kMZA^8ZAy52^R{+-YG!J5q^YP&$t9F`&J8*KzV4t3ZZZJ>~XP7}Bs z<}$a~2r_E?4rlN=(}RBkF~6rBo}Sz7#r{X49&!gODP+TcB*@uq57EII-_>qWEt44B z`5o+tysMLY*Dq^n@4_vzKRu3We5|DI+i%NV=Z|)QAl{di_@%07*qoM6N<$f(5Fv<^TWy diff --git a/example-expo/assets/icon.png b/example-expo/assets/icon.png deleted file mode 100644 index a0b1526fc7b78680fd8d733dbc6113e1af695487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22380 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 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 0000000000000000000000000000000000000000..5ffefc5bb57a3d7b39ec6ff4e96979226522cc49 GIT binary patch literal 17549 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

f!0B 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..77484ebdbca253297baea4a7d416233aa47a45c0 GIT binary patch literal 4140 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..28630679fd5be26be3eace15c9e3b53dd413617b GIT binary patch literal 4129 zcmV++5Z>>JP)t!V0-Fec2*42-BCv_T5CL`s$PpMvfE#NrVuxO*}Sf-u(RG(>>B;GI?}s zWa0+K3;2Qm@y@4%CJ{o&O8}xi^Xa=6_L&=)LBzbVz|R~7oDf35+^1`wZoIJ1-2n6| z=1RoR9o;5`5EA0rr^~)Yy%#a3h!a9cHP^03QSU6~UdTWAA733soDf24nfr9*1^tWb zNNX`~tZ|6=2S*4Yqziahdep^A|I4+&3pqjye>w^|A%ye+H1)3WbhM&c%>BvW8;6ij z9U+8}zL@%S>oxY1YeAKmHz(=r+aZJy66K_aS zLI_zqCtiC$mmZw8%o`s$T`PnTvKcPCg?^bkC@JPDYK0I&$mTflf_{@aAS&iw$fy3t zonzDrA%tui?7;Xo6^EYknZr*$b%YQ?HVqbO-lh%+in+JSPyCM)M+hNgEbM!2zKDJJ zA2$o#D})d-TB7cjkY$eOm8m0y5HdDoPptp&%rbAR@&`u<84Zt*kM8E?X8BV%K0bES z>C}-I%b|UHkri_&{)r=mjDq=m?hX$Rmp?6ccX#gK;J}d=$;^xSq5hARnA0jJWPF^S zp1SMn>)>a5dwcHT;bHl+5AN^p-N%m~U0ZN-A!`M`^LzQ@Z}q`}8v(FDX0uuLICNl4 zGxzuR9oZh>D){#8+v@KRA3hX|5yS>bI5|0SyA0|nCgcEhd3ky1K7TG=@tp+um5eAN->jK3oO%Sp9G#t}GOt*Ds$L~i+NABlOZY4vuAGl9SfolYb4?|xh$3D&Qi-`GqH&Wma zjD=vK1H&p2QHQ`|5PbUd$q}-xpbyv1GVz_E#N4|+9T_cn&D?To_IKlRIdEGDIxn7} zzkU056@!cEDaD*@D-iM2L(dp6Z1BJoGugh`^#{#c&YsusDOQBSGT-WO5LoT#Y+t@S zZMlxYfE2RkC6eOTuV2e!@HdXbP2t_Ucfs#mU0tni6t7;rigs+|hlL#i@{3tY7T;kLTHy$zP@`}gn5bIEbIhWGE^7cWqe z1uo(SLmnt^tbY3d=R`Z{wvV-`t^2dHvurdC>hAaN->bieG#1)eNwv?s>_Nf2QM(e zyuEm@j>CCSe>jKzzQ#XicvhxRLcY1SNR=Ds#CiITWtQHt z-PnY7M88TMle?Z2v1pby*AZ$Hw1*vE=o6uHqCX0@-I4Hr$SYMJC4CkkALQ3|%#DC2 zOBTN@M#K31o!f$UN8ZE}T6bi|-Vjjv*i#Y~U?k14EQTW0_PXv<0)}g|f>}yT%yHgaviN6R!MwDQ zgK~z#FKs^yjbGGNN$4i(1AROOJ}V6Y4UN~4@U(El|8ln&5pBdA)`=1dnIcoE8Hp`3Yz=qiy-5T7G|2yQP3%LNS_~#xpT4K}h+(SEk9_@p+ z4Q?$xEquBImfZDAZVD;Cy}m(}xDV{0lZ3Yp1T3rcGCh^;%c!s*r7;S zh^cGLxd}>UwVENs9F#myN%e)8m}5+b+Re_$C24=X4GKu*7n=o*?1AWX^kQC&~I`(gH1=S_`f2pw`lp!DdAsoh0|bDP}2`3Xm3IiF!#TTQ_2*>Oqlv zm>8;qt`9(S=ytOY-*N-9ea#@6X32J9O=PS zD(1OR#9R^))`C~kxpT9e6m2%zO1V=~pT%yxeUlQo(sQQTvZj9TdJ0Xf$vf9iK}bkT zZgio#4H+h(_P2yWC`)GhYh)UHq4PE%3q!En-#3u+qM1CtWSdpH$UrYtz#;@eFLKaZ zhRQMeSpB*Febb9O`r4FCFZLK}QP5D}Z9RfrxT3e|ML!$j+ESZoVPR6~Ia7<6YW8>; z64YUA47M2yC5w1!+AMc$Y!Tv^zzWd08}W{b5bAY_gdJGA^GRov2E>Xph=CSpS@9{W z(~zHtgsiF*u&OL{9)MMwHP_tc+uK3(FTgFLod_F@|;l zboH47?Po@Q+GSv;Y1%CVca6R-JFT>?hb{|K+W5>%rmQh)edc{H8p@&jK$PlxydfwA&ez86fHKD3I3(8jCcw4g9i};8`PR6EkZxtVfUW4KrW({8I7%8(_7Ze^l`` zeTuoAdZrq{uE2}M_l5>bOjKe6D};Soj`G9=-7cTkWvz*gwkt_vV)qbONh_2t1hOkv zQ^&>#4ZxWDtVa0ljkL7>+7OF@MvARwts%B?RY_fUjjFQ5TuHJ4R!Ag6H?%%hfZDb! z371TzKH92BE=0_STMf~IKE)i^I9xO!I+LszgD~0!1gQ|ans`$yx=1Mt$yzb8>z|Yr zR*Svq*iKp_=Ai*4bpuhr`9hY7z0rkYoskc|kA!QGfn)2st(c=sl9#S0lr41ANUh$C ziP#Wv#Id;WK5rZ`YdB|}_bKMFo0c}HVvBKXIr|XvkdCyy-jRhQf`9#TixDm9?_10P z?xHmfm2z=}IzZ`?`P8vO>1sI1iVCd= z9Af!?Y_`^?J11pz*7oh1q$%A*E7sq&+po*6iJ^09Q13PUAMH1?W&GgZhdTa`;r8{c zWC$^rd{d1tJN=b3lx_S}8SObst_RjzuA|DTO-q|4=*TCxh$yr$%aRgwvB>&bG$hw% zecZOpxtnJw@0z-+WnC)?LsoC@n)(v+n0qC($}_bYm-O%0ecm5h;+8?s$d-M0U4 zh}BDvCykh&_#a>02*8tQySS#$XafEA9)P^xym?b~9+XewPUTHHeH@!BE0D5I6YoWB zdpen3N+P#Pl60?km4n3CeF3;2};B4E}B6;gRsEM_Q9rJ=L3`CTRjl0L+vRF!@ z#pLZkwSCr>L*B?GQD(?X-g-1bt|pVo`RXq(=J_3x2-#tVni0m%C9}KInL``2(6Qu0T6?$#?`BxN>z3>sbFba^ z@`v7=WgMG05V8iu4Iqm}v|C`P<@*|O2dv{f6!M+rnP&ejzj%e8dFy=cM!Zi8A$@Ur zdK&f9ru@o;jD)#Q&mM5tT@z`Ylbt8^zzq*03E2W};2ziiF|kK1o?Z?*b%g9F5OTOy zBr*mf4%Z5H9vo@J!iz?BwbZQ3O*K;HJ{>N1N>qfz98YF$9U+8}F>>jJ{KEd{By^+~ zbj&pm93g~|F@Xq{*P#z$ZyGxFX-xKJgrb ziMd7z`+N5!>{CYwA-f3#d*;&*pT70wM#-@jF}HYe$NQc{)RFM_{~RHNY#0A`PvSjq fe%`N3ye_{SWhRB?0xdI@wBhGxg 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 UD9cJ(z+Vc8_aY0~Jks(?}v7_xUud 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 literal 0 HcmV?d00001 diff --git a/example-expo/assets/images/logo-glow.png b/example-expo/assets/images/logo-glow.png new file mode 100644 index 0000000000000000000000000000000000000000..edc99be1b65ef9ce1bbc04b88d9c2994d74246fc GIT binary patch literal 331624 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

h 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 z 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

(T;14;$?I?&4ux?zQ#4`il1V->EP| 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�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

k!4>R@%qQ0}X;r0aNwI@Cb8FaNpAR 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

p$Nf`sWhN9ZlZ}5n9H171I?`d6Jxm!1n;ow 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 J?wX%|{(mrMfIa{K 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 90d8d4c2..00000000 --- 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 ed285c2e..00000000 --- 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 3cf84897..00000000 --- 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 c52c2c68019b49c56da4faf7d8835a8392cfef7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59836 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 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 3cf84897..00000000 --- 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 33ddf20bb375ee42a98cb8a8c0952ba7f3ac62f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blZci7-kP61+AN(u~oWGg>p6AXe Q0tz#Dy85}Sb4q9e0EL(iEdT%j diff --git a/example-expo/ios/exampleexpo/Info.plist b/example-expo/ios/exampleexpo/Info.plist deleted file mode 100644 index a57111dd..00000000 --- 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 5bb83c5d..00000000 --- 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 ed03a529..00000000 --- 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 750be020..00000000 --- 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 e11d920b..00000000 --- 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 f683276c..00000000 --- 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 25181b6c..00000000 --- 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 b2ffafbf..00000000 --- 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 00000000..6c38f690 --- /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 d101d6fa..4daa8f19 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 00000000..055d15b1 --- /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 551457ea..00000000 --- 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 864f4600..00000000 --- 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 4d0dc5c3..00000000 --- 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 a1b5419f..00000000 --- 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 0e00a165..00000000 --- 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 414a4eda..00000000 --- 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 9a2984bf..00000000 --- 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 c0e6ac12..00000000 --- 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 dd06f9c1..00000000 --- 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 cc2db6bc..00000000 --- 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 29366514..00000000 --- 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 3f14cec6..00000000 --- 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 d650a155..00000000 --- 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 a1084771..00000000 --- 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 c96d169d..00000000 --- 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 867904dc..00000000 --- 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 39d6d9ff..00000000 --- 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 66395ca6..00000000 --- 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 0c412e25..00000000 --- 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 2df86427..00000000 --- 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 9c8479a4..00000000 --- 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 28f689f4..00000000 --- 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 d10986d6..00000000 --- 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 472fafd1..00000000 --- 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 375a0550..00000000 --- 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 229fe96c..00000000 --- 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 d7af5e41..00000000 --- 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 99f21d89..00000000 --- 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 01847a81..00000000 --- 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 1b3715b3..00000000 --- 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 8cf621ad..00000000 --- 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 3b0de206..00000000 --- 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 6887aa49..00000000 --- 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 472fafd1..00000000 --- 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 8b6e2a13..00000000 --- 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 a4a07417..00000000 --- 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 7679fd50..00000000 --- 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 7c2ad4bf..00000000 --- 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 d5b1621b..00000000 --- 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 93465a34..00000000 --- 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 3689e2fc..00000000 --- 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 93851e47..00000000 --- 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 722beab2..00000000 --- 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 cc091c08..00000000 --- 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 6770bc11..00000000 --- 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 44bede3a..00000000 --- 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 b9567f60..2e9a6695 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/ios/BlePlxExample/Info.plist b/example/ios/BlePlxExample/Info.plist index 73864d37..452d36d5 100644 --- a/example/ios/BlePlxExample/Info.plist +++ b/example/ios/BlePlxExample/Info.plist @@ -33,6 +33,8 @@ NSAllowsLocalNetworking + NSBluetoothAlwaysUsageDescription + This app uses Bluetooth to test BLE library functionality NSLocationWhenInUseUsageDescription RCTNewArchEnabled diff --git a/example/src/App.tsx b/example/src/App.tsx index 9a8a7849..2815767e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -6,6 +6,7 @@ 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 } @@ -23,6 +24,11 @@ export type RootStackParamList = { isIndicatable: boolean } } + L2CAP: { + manager: BleManager + deviceId: string + psm: number + } } const Stack = createNativeStackNavigator() @@ -60,6 +66,11 @@ export default function App() { component={CharacteristicScreen} options={{ title: 'Characteristic' }} /> + ) diff --git a/example/src/screens/CharacteristicScreen.tsx b/example/src/screens/CharacteristicScreen.tsx index bc775fa7..34860111 100644 --- a/example/src/screens/CharacteristicScreen.tsx +++ b/example/src/screens/CharacteristicScreen.tsx @@ -16,6 +16,11 @@ 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; @@ -23,7 +28,14 @@ export default function CharacteristicScreen({ route }: Props) { 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 () => { @@ -38,7 +50,11 @@ export default function CharacteristicScreen({ route }: Props) { serviceUuid, characteristicUuid, ); - setValue(result.value ?? '(null)'); + 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)); } @@ -53,8 +69,10 @@ export default function CharacteristicScreen({ route }: Props) { 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]); @@ -62,11 +80,18 @@ export default function CharacteristicScreen({ route }: Props) { 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, event) => { + (error: any, event: any) => { if (error) { console.warn('Monitor error:', error); setMonitoring(false); @@ -74,6 +99,12 @@ export default function CharacteristicScreen({ route }: Props) { } 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); + } } }, ); @@ -81,6 +112,7 @@ export default function CharacteristicScreen({ route }: Props) { } else { monitorSub.current?.remove(); monitorSub.current = null; + setStoppedAtCount(sampleCountRef.current); setMonitoring(false); } }, @@ -90,12 +122,12 @@ export default function CharacteristicScreen({ route }: Props) { return ( Characteristic - {characteristicUuid} - Service: {serviceUuid} + {characteristicUuid} + Service: {serviceUuid} Properties: - + {[ properties.isReadable && 'Readable', properties.isWritableWithResponse && 'Writable (response)', @@ -114,6 +146,30 @@ export default function CharacteristicScreen({ route }: Props) { + {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 && ( + + setWriteValue('QQ==')}> + 1 byte + + setWriteValue('QUJDREVGR0hJSktMTU5PUFFSU1Q=')}> + 20 bytes + + Write + {writeError && ( + + {writeError} + + )} )} @@ -167,6 +238,8 @@ const styles = StyleSheet.create({ 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, @@ -184,6 +257,9 @@ const styles = StyleSheet.create({ 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', @@ -192,4 +268,5 @@ const styles = StyleSheet.create({ 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 index 2d102d5a..9b95c108 100644 --- a/example/src/screens/DeviceScreen.tsx +++ b/example/src/screens/DeviceScreen.tsx @@ -6,24 +6,45 @@ import { FlatList, StyleSheet, Alert, + TextInput, + Platform, } from 'react-native'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; -import type { CharacteristicInfo } from 'react-native-ble-plx'; import type { RootStackParamList } from '../App'; type Props = NativeStackScreenProps; -interface ServiceGroup { - serviceUuid: string; - characteristics: CharacteristicInfo[]; -} +// 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); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [services, _setServices] = useState([]); + 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(() => {}); @@ -32,11 +53,14 @@ export default function DeviceScreen({ navigation, route }: Props) { const discoverServices = useCallback(async () => { setDiscovering(true); try { - await manager.discoverAllServicesAndCharacteristics(deviceId); - // The v4 API returns DeviceInfo from discover, but characteristics - // need to be read separately. For now we show the discovery was successful. - // In a full implementation we'd query for services/characteristics. - Alert.alert('Discovery', 'Services and characteristics discovered. Characteristic browsing requires servicesForDevice() API (not yet in v4 spec).'); + 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 { @@ -44,6 +68,17 @@ export default function DeviceScreen({ navigation, route }: Props) { } }, [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); @@ -53,12 +88,69 @@ export default function DeviceScreen({ navigation, route }: Props) { } }, [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}} + {deviceName || 'Unknown Device'} + ID: {deviceId} + {mtu != null && MTU: {mtu}} @@ -80,46 +172,98 @@ export default function DeviceScreen({ navigation, route }: Props) { - {services.length > 0 && ( + {Platform.OS === 'android' && ( + + + Request MTU 247 + + {mtuRequestStatus != null && ( + + {mtuRequestStatus} + + )} + + )} + + {serviceUuids.length > 0 && ( item.serviceUuid} - renderItem={({ item }) => ( - - Service: {item.serviceUuid} - {item.characteristics.map((char) => ( - - navigation.navigate('Characteristic', { - manager, - deviceId, - serviceUuid: item.serviceUuid, - characteristicUuid: char.uuid, - properties: { - isReadable: char.isReadable, - isWritableWithResponse: char.isWritableWithResponse, - isWritableWithoutResponse: char.isWritableWithoutResponse, - isNotifying: char.isNotifying, - isIndicatable: char.isIndicatable, - }, - }) - }> - {char.uuid} - - {[ - char.isReadable && 'Read', - (char.isWritableWithResponse || char.isWritableWithoutResponse) && 'Write', - char.isNotifying && 'Notify', - char.isIndicatable && 'Indicate', - ] - .filter(Boolean) - .join(', ')} + testID="service-list" + data={serviceUuids} + keyExtractor={(item) => 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 + + + )} )} /> @@ -135,6 +279,8 @@ const styles = StyleSheet.create({ 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, @@ -144,14 +290,27 @@ const styles = StyleSheet.create({ buttonDisabled: { backgroundColor: '#ccc' }, disconnectButton: { backgroundColor: '#FF3B30' }, buttonText: { color: '#fff', fontWeight: '600' }, - serviceGroup: { marginBottom: 16 }, - serviceUuid: { fontSize: 14, fontWeight: '600', marginBottom: 6 }, + 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: 8, - paddingHorizontal: 12, + paddingVertical: 10, + paddingHorizontal: 8, borderBottomWidth: 1, borderBottomColor: '#eee', }, - charUuid: { fontSize: 13 }, - charProps: { fontSize: 11, color: '#888', marginTop: 2 }, + 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 00000000..a35d6e98 --- /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/ScanScreen.tsx b/example/src/screens/ScanScreen.tsx index 2f2cad08..8ee13dd7 100644 --- a/example/src/screens/ScanScreen.tsx +++ b/example/src/screens/ScanScreen.tsx @@ -2,8 +2,9 @@ import React, { useEffect, useState, useCallback, useRef } from 'react'; import { View, Text, - FlatList, TouchableOpacity, + Pressable, + TextInput, StyleSheet, Platform, PermissionsAndroid, @@ -37,8 +38,14 @@ export default function ScanScreen({ navigation, route }: Props) { 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); @@ -46,6 +53,7 @@ export default function ScanScreen({ navigation, route }: Props) { requestAndroidPermissions().then((ok) => { if (!ok) { + setScanError('BLE permissions not granted'); Alert.alert('Permissions', 'BLE permissions not granted'); } }); @@ -54,13 +62,18 @@ export default function ScanScreen({ navigation, route }: Props) { }, [manager]); const startScan = useCallback(() => { + manager.stopDeviceScan(); devicesRef.current = new Map(); setDevices(new Map()); setScanning(true); + setConnectionStatus(''); + setScanError(null); - manager.startDeviceScan(null, null, (error, result) => { + 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; } @@ -77,23 +90,52 @@ export default function ScanScreen({ navigation, route }: Props) { }, [manager]); const connectToDevice = useCallback( - async (deviceId: string) => { + async (device: ScanResult) => { + setConnectionStatus(`Connecting to ${device.name || device.id}...`); stopScan(); try { - const device = await manager.connectToDevice(deviceId); - navigation.navigate('Device', { manager, deviceId: device.id, deviceName: device.name }); + // 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()); + 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 State: {bleState} + + BLE Scanner ({Platform.OS}) + + BLE State: {bleState} + + {connectionStatus ? ( + {connectionStatus} + ) : null} + + {scanError && ( + + {scanError} + + )} - + + + setFilterEnabled(!filterEnabled)}> + + {filterEnabled ? 'Filter: Test Service UUID' : 'Filter: None'} + + + + {deviceList.length} device(s) found - item.id} - renderItem={({ item }) => ( - connectToDevice(item.id)}> + + {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' }, - stateText: { fontSize: 16, fontWeight: '600', marginBottom: 12 }, + 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', @@ -149,13 +213,22 @@ const styles = StyleSheet.create({ }, 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/integration-tests/hardware/maestro/01-scan-connect-discover.yaml b/integration-tests/hardware/maestro/01-scan-connect-discover.yaml new file mode 100644 index 00000000..582dd8e1 --- /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 00000000..a2a2cbc3 --- /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 00000000..e3b72f91 --- /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 00000000..c377e4e4 --- /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 00000000..4c2bf3d0 --- /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 00000000..816d02bc --- /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 00000000..c56b4399 --- /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 00000000..0dca92f6 --- /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 00000000..074e784f --- /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 00000000..184e6159 --- /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 00000000..e02fc45d --- /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 00000000..3deb9b27 --- /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 00000000..594856fe --- /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 00000000..aa8b96b9 --- /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 00000000..884df1cf --- /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 00000000..ca2c56b0 --- /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 00000000..58d8a12b --- /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 00000000..f5c62c2e --- /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 00000000..935ebe86 --- /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 index dd22e39d..903f3b25 100644 --- a/integration-tests/hardware/maestro/README.md +++ b/integration-tests/hardware/maestro/README.md @@ -1,127 +1,133 @@ -# Maestro Hardware Integration Tests +# BLE E2E Tests (Maestro) -These flows test the react-native-ble-plx library against real BLE hardware -using the [Maestro](https://maestro.mobile.dev) mobile UI testing framework. +Automated end-to-end tests for react-native-ble-plx using two physical devices. -## Prerequisites +## Setup -### Hardware +### Devices +- **iPhone**: UDID `00008130-000A34C12021401C` +- **Android**: ID `1A211FDF60055L` -- A **BlePlxTest** peripheral must be flashed with the test firmware and powered - on within BLE range of the device under test. -- The firmware is located in `integration-tests/hardware/peripheral-firmware/`. +### 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` | -### App - -- The example app (`example/`) must be built and installed on the test device. -- Android package: `com.bleplxexample` -- iOS bundle ID: `org.reactjs.native.example.BlePlxExample` +### 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 -BLE permissions must be granted before the flows run. Maestro does not handle -system permission dialogs reliably on all platforms. - #### Android (API 31+) - -Run the following `adb` commands after installing the app: - ```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 ``` -For Android API 28–30 (legacy location permission only): - -```bash -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. -iOS BLE (`NSBluetoothAlwaysUsageDescription`) and location permissions must be -granted manually on first launch. Open the app on the device, accept all -permission prompts, then terminate and re-launch before running the flows. - -Alternatively, if using an iOS simulator with a paired BLE adapter, no -permission prompt appears. - -## Running the Flows - -Ensure the `maestro` CLI is installed (`brew install maestro` on macOS). - -### Individual flows +## Running Tests ```bash -# Full happy path: scan, connect, read characteristic, disconnect -maestro test integration-tests/hardware/maestro/scan-pair-sync.yaml +# Android as scanner, iPhone as peripheral +./run-e2e.sh android -# Write a value to echo characteristic and read it back -maestro test integration-tests/hardware/maestro/write-read-roundtrip.yaml +# iPhone as scanner, Android as peripheral +./run-e2e.sh ios -# Simulate unexpected disconnection and verify recovery -maestro test integration-tests/hardware/maestro/disconnect-recovery.yaml - -# Sustained indication stream for 30 seconds -maestro test integration-tests/hardware/maestro/indicate-stress.yaml +# Run a specific test only +./run-e2e.sh android 03 # write-read-echo test +./run-e2e.sh ios 04 # notify-stream test ``` -### All flows in sequence - -```bash -maestro test integration-tests/hardware/maestro/ -``` - -## Flow Descriptions - -| File | What it tests | -|------|---------------| -| `scan-pair-sync.yaml` | Full happy path: BLE scan, connect, discover, read, disconnect | -| `write-read-roundtrip.yaml` | Write to echo characteristic and verify read-back matches | -| `disconnect-recovery.yaml` | Unexpected peripheral disconnect; re-scan and reconnect | -| `indicate-stress.yaml` | 30-second indication stream stress test on CharacteristicScreen | - -## TestIDs Reference - -The example app uses the following `testID` props for Maestro assertions: - -**ScanScreen** (`example/src/screens/ScanScreen.tsx`) - +## 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 discovered devices | -| `device-item-{id}` | Individual device row (id = MAC address or UUID) | - -**DeviceScreen** (`example/src/screens/DeviceScreen.tsx`) +| `device-list` | FlatList of devices | +| `device-item-{id}` | Individual device row | +### DeviceScreen | testID | Element | |--------|---------| | `discover-btn` | Discover Services button | | `disconnect-btn` | Disconnect button | -| `service-{uuid}` | Service group container (populated after discovery) | -| `char-{uuid}` | Characteristic row (tap to navigate to CharacteristicScreen) | - -**CharacteristicScreen** (`example/src/screens/CharacteristicScreen.tsx`) - +| `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 (visible when characteristic is readable) | -| `write-input` | TextInput for base64 write value | -| `write-btn` | Write button (visible when characteristic is writable) | -| `monitor-toggle` | Switch to enable/disable notifications or indications | -| `value-display` | Text showing the current characteristic value | - -## Notes - -- The `disconnect-recovery.yaml` flow requires a manual or automated step to - trigger an unexpected disconnection from the firmware side. Annotated - comments in the flow indicate where to insert a relay command or serial call. -- The `write-read-roundtrip.yaml` flow uses base64-encoded values. "Hello" - encodes to `SGVsbG8=`. -- Discovery on DeviceScreen currently shows an alert because the v4 API's - `servicesForDevice()` is not yet implemented; characteristic rows (char-{uuid}) - only appear once that API is available. Until then, flows that navigate to a - characteristic require the service list to be populated by the firmware. +| `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 00000000..3148d21e --- /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 index fe99bfbe..bf711828 100644 --- a/integration-tests/hardware/maestro/disconnect-recovery.yaml +++ b/integration-tests/hardware/maestro/disconnect-recovery.yaml @@ -24,30 +24,39 @@ appId: com.bleplxexample - launchApp: clearState: true -- assertVisible: - id: "scan-start-btn" +- extendedWaitUntil: + visible: + id: "scan-start-btn" timeout: 5000 -- assertVisible: - text: "PoweredOn" +- extendedWaitUntil: + visible: + id: "ble-state" timeout: 10000 - tapOn: id: "scan-start-btn" -- assertVisible: - text: "BlePlxTest" +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" timeout: 15000 # --------------------------------------------------------------------------- # 2. Connect to BlePlxTest # --------------------------------------------------------------------------- - tapOn: - text: "BlePlxTest" + id: "device-BlePlxTest" # Confirm we have reached DeviceScreen (connected state) -- assertVisible: - id: "discover-btn" +- extendedWaitUntil: + visible: + id: "discover-btn" timeout: 10000 - assertVisible: @@ -69,8 +78,9 @@ appId: com.bleplxexample # The library detects link loss via onDeviceDisconnected. The app calls # navigation.goBack(), returning to ScanScreen. -- assertVisible: - id: "scan-start-btn" +- extendedWaitUntil: + visible: + id: "scan-start-btn" timeout: 15000 # The scan-start-btn being visible confirms we are back on ScanScreen, @@ -85,15 +95,24 @@ appId: com.bleplxexample - tapOn: id: "scan-start-btn" -- assertVisible: - text: "BlePlxTest" +# 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: - text: "BlePlxTest" + id: "device-BlePlxTest" -- assertVisible: - id: "discover-btn" +- extendedWaitUntil: + visible: + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- @@ -102,6 +121,7 @@ appId: com.bleplxexample - tapOn: id: "disconnect-btn" -- assertVisible: - id: "scan-start-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 index 42502c01..43e4a6b6 100644 --- a/integration-tests/hardware/maestro/indicate-stress.yaml +++ b/integration-tests/hardware/maestro/indicate-stress.yaml @@ -4,7 +4,7 @@ # over 30 seconds without dropping the connection, leaking memory, or crashing. # # The BlePlxTest peripheral sends indications on the Indicate Stream -# characteristic (fff3) continuously while monitoring is active. +# characteristic continuously while monitoring is active. # This flow enables the monitor toggle, waits 30 seconds with periodic # liveness checks, then disables monitoring and disconnects. # @@ -12,8 +12,8 @@ # - 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 on fff3 -# that sends indications at a regular cadence when enabled. +# - 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 @@ -28,51 +28,60 @@ appId: com.bleplxexample - launchApp: clearState: true -- assertVisible: - id: "scan-start-btn" +- extendedWaitUntil: + visible: + id: "scan-start-btn" timeout: 5000 -- assertVisible: - text: "PoweredOn" +- extendedWaitUntil: + visible: + id: "ble-state" timeout: 10000 - tapOn: id: "scan-start-btn" -- assertVisible: - text: "BlePlxTest" +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" timeout: 15000 # --------------------------------------------------------------------------- # 2. Connect to BlePlxTest # --------------------------------------------------------------------------- - tapOn: - text: "BlePlxTest" + id: "device-BlePlxTest" -- assertVisible: - id: "discover-btn" +- extendedWaitUntil: + visible: + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- -# 3. Discover services and dismiss the alert +# 3. Discover services # --------------------------------------------------------------------------- - tapOn: id: "discover-btn" -- assertVisible: - text: "Discovery" - timeout: 8000 -- tapOn: - text: "OK" +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 # --------------------------------------------------------------------------- -# 4. Navigate to the Indicate Stream characteristic (fff3) +# 4. Navigate to the Indicate Stream characteristic # --------------------------------------------------------------------------- - tapOn: - id: "char-fff3" + id: "char-indicate-stream" -- assertVisible: - id: "monitor-toggle" +- extendedWaitUntil: + visible: + id: "monitor-toggle" timeout: 5000 # Confirm the value-display is present before enabling monitoring @@ -97,13 +106,9 @@ appId: com.bleplxexample # --------------------------------------------------------------------------- # 6. Run for 30 seconds with periodic liveness checks -# -# Maestro does not have a native timer loop, so we use a sequence of -# waitForAnimationToEnd calls as coarse 5-second delays. Each checkpoint -# verifies the value-display is still updating and no error has appeared. # --------------------------------------------------------------------------- -# t ≈ 5 s +# t ~ 5 s - waitForAnimationToEnd: timeout: 5000 - assertVisible: @@ -111,7 +116,7 @@ appId: com.bleplxexample - assertNotVisible: text: "Monitor error" -# t ≈ 10 s +# t ~ 10 s - waitForAnimationToEnd: timeout: 5000 - assertVisible: @@ -119,7 +124,7 @@ appId: com.bleplxexample - assertNotVisible: text: "Monitor error" -# t ≈ 15 s +# t ~ 15 s - waitForAnimationToEnd: timeout: 5000 - assertVisible: @@ -127,7 +132,7 @@ appId: com.bleplxexample - assertNotVisible: text: "Monitor error" -# t ≈ 20 s +# t ~ 20 s - waitForAnimationToEnd: timeout: 5000 - assertVisible: @@ -135,7 +140,7 @@ appId: com.bleplxexample - assertNotVisible: text: "Monitor error" -# t ≈ 25 s +# t ~ 25 s - waitForAnimationToEnd: timeout: 5000 - assertVisible: @@ -143,7 +148,7 @@ appId: com.bleplxexample - assertNotVisible: text: "Monitor error" -# t ≈ 30 s — final check +# t ~ 30 s — final check - waitForAnimationToEnd: timeout: 5000 - assertVisible: @@ -169,13 +174,15 @@ appId: com.bleplxexample # --------------------------------------------------------------------------- - back -- assertVisible: - id: "disconnect-btn" +- extendedWaitUntil: + visible: + id: "disconnect-btn" timeout: 5000 - tapOn: id: "disconnect-btn" -- assertVisible: - id: "scan-start-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 00000000..29e5e505 --- /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 index ad531258..dc133d06 100644 --- a/integration-tests/hardware/maestro/scan-pair-sync.yaml +++ b/integration-tests/hardware/maestro/scan-pair-sync.yaml @@ -31,43 +31,43 @@ appId: com.bleplxexample # --------------------------------------------------------------------------- # 2. Wait for BLE to be powered on, then start scanning -# -# The BLE state is displayed as inline text: "BLE State: PoweredOn". -# We wait for it to appear before scanning. # --------------------------------------------------------------------------- -- assertVisible: - text: "PoweredOn" +- extendedWaitUntil: + visible: + id: "ble-state" timeout: 10000 - tapOn: id: "scan-start-btn" # --------------------------------------------------------------------------- -# 3. Wait for the BlePlxTest peripheral to appear in the device list +# 3. Filter and wait for the BlePlxTest peripheral to appear # --------------------------------------------------------------------------- +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + - assertVisible: id: "device-list" -- assertVisible: - text: "BlePlxTest" +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" timeout: 15000 # --------------------------------------------------------------------------- # 4. Tap the device row to connect -# -# The device row testID is device-item-{MAC/UUID}. Since the exact ID is -# unknown at authoring time, we tap by the displayed name instead. # --------------------------------------------------------------------------- - tapOn: - text: "BlePlxTest" + id: "device-BlePlxTest" # --------------------------------------------------------------------------- # 5. Wait for the DeviceScreen to load -# -# The DeviceScreen shows the discover-btn once the connection is established. # --------------------------------------------------------------------------- -- assertVisible: - id: "discover-btn" +- extendedWaitUntil: + visible: + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- @@ -76,28 +76,24 @@ appId: com.bleplxexample - tapOn: id: "discover-btn" -# The app alerts on discovery completion. Dismiss the alert. -- assertVisible: - text: "Discovery" - timeout: 8000 -- tapOn: - text: "OK" +# Wait for the test service to appear and auto-expand +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 # --------------------------------------------------------------------------- -# 7. Navigate to a characteristic -# -# If the service list is populated (future v4 API), tap the first visible -# characteristic. The testIDs follow the pattern char-{uuid}. -# For the read-counter characteristic on BlePlxTest firmware use the known UUID. +# 7. Navigate to the Read Counter characteristic # --------------------------------------------------------------------------- - tapOn: - id: "char-fff1" + id: "char-read-counter" # --------------------------------------------------------------------------- # 8. On CharacteristicScreen: read the value and verify it appears # --------------------------------------------------------------------------- -- assertVisible: - id: "read-btn" +- extendedWaitUntil: + visible: + id: "read-btn" timeout: 5000 - tapOn: @@ -105,8 +101,9 @@ appId: com.bleplxexample # value-display starts as "(none)"; after a successful read it shows a base64 # encoded value. Assert it is no longer showing "(none)". -- assertVisible: - id: "value-display" +- extendedWaitUntil: + visible: + id: "value-display" timeout: 5000 - assertNotVisible: text: "Value: (none)" @@ -116,14 +113,16 @@ appId: com.bleplxexample # --------------------------------------------------------------------------- - back -- assertVisible: - id: "disconnect-btn" +- extendedWaitUntil: + visible: + id: "disconnect-btn" timeout: 5000 - tapOn: id: "disconnect-btn" # After disconnecting, navigation returns to ScanScreen -- assertVisible: - id: "scan-start-btn" +- 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 index a236bb2d..200d9133 100644 --- a/integration-tests/hardware/maestro/write-read-roundtrip.yaml +++ b/integration-tests/hardware/maestro/write-read-roundtrip.yaml @@ -1,15 +1,15 @@ # Maestro flow: Write → Read Roundtrip # -# Verifies that a value written to the Write Echo characteristic (fff2 on -# BlePlxTest firmware) can be read back with the same content, confirming -# bidirectional characteristic I/O works end-to-end. +# 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 on fff2 -# that echoes back whatever value was last written. +# - 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 @@ -24,12 +24,14 @@ appId: com.bleplxexample - launchApp: clearState: true -- assertVisible: - id: "scan-start-btn" +- extendedWaitUntil: + visible: + id: "scan-start-btn" timeout: 5000 -- assertVisible: - text: "PoweredOn" +- extendedWaitUntil: + visible: + id: "ble-state" timeout: 10000 # --------------------------------------------------------------------------- @@ -38,37 +40,44 @@ appId: com.bleplxexample - tapOn: id: "scan-start-btn" -- assertVisible: - text: "BlePlxTest" +- tapOn: + id: "scan-search" +- inputText: "BlePlxTest" +- hideKeyboard + +- extendedWaitUntil: + visible: + id: "device-BlePlxTest" timeout: 15000 - tapOn: - text: "BlePlxTest" + id: "device-BlePlxTest" -- assertVisible: - id: "discover-btn" +- extendedWaitUntil: + visible: + id: "discover-btn" timeout: 10000 # --------------------------------------------------------------------------- -# 3. Discover services and dismiss the alert +# 3. Discover services # --------------------------------------------------------------------------- - tapOn: id: "discover-btn" -- assertVisible: - text: "Discovery" - timeout: 8000 -- tapOn: - text: "OK" +- extendedWaitUntil: + visible: + id: "service-test" + timeout: 10000 # --------------------------------------------------------------------------- -# 4. Navigate to the Write Echo characteristic (fff2) +# 4. Navigate to the Write Echo characteristic # --------------------------------------------------------------------------- - tapOn: - id: "char-fff2" + id: "char-write-echo" -- assertVisible: - id: "write-input" +- extendedWaitUntil: + visible: + id: "write-input" timeout: 5000 # --------------------------------------------------------------------------- @@ -78,15 +87,16 @@ appId: com.bleplxexample # --------------------------------------------------------------------------- - tapOn: id: "write-input" -- clearText +- eraseText: 50 - inputText: "SGVsbG8=" - tapOn: id: "write-btn" # Dismiss the "Value written successfully" alert -- assertVisible: - text: "Write" +- extendedWaitUntil: + visible: + text: "Value written successfully" timeout: 5000 - tapOn: text: "OK" @@ -98,8 +108,9 @@ appId: com.bleplxexample id: "read-btn" # The value-display should now show the echoed base64 value -- assertVisible: - id: "value-display" +- extendedWaitUntil: + visible: + id: "value-display" timeout: 5000 - assertVisible: text: "SGVsbG8=" @@ -109,13 +120,15 @@ appId: com.bleplxexample # --------------------------------------------------------------------------- - back -- assertVisible: - id: "disconnect-btn" +- extendedWaitUntil: + visible: + id: "disconnect-btn" timeout: 5000 - tapOn: id: "disconnect-btn" -- assertVisible: - id: "scan-start-btn" +- extendedWaitUntil: + visible: + id: "scan-start-btn" timeout: 8000 diff --git a/ios/BLEActor.swift b/ios/BLEActor.swift index 11de8039..f97ccddd 100644 --- a/ios/BLEActor.swift +++ b/ios/BLEActor.swift @@ -212,6 +212,8 @@ actor BLEActor { 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 { @@ -394,11 +396,15 @@ actor BLEActor { func openL2CAPChannel(deviceId: String, psm: UInt16) async throws -> Int { let wrapper = try getPeripheral(deviceId: deviceId) - await wrapper.openL2CAPChannel(psm: CBL2CAPPSM(psm)) - // The actual channel is received via the peripheral delegate - // For now, return a pending channel ID - // TODO: Wire up CBPeripheral didOpenL2CAPChannel callback - throw BleError(code: .l2capOpenFailed, message: "L2CAP channel open is pending — callback not yet wired") + 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 { diff --git a/ios/BlePlx.mm b/ios/BlePlx.mm index 50967d9b..e831f0c4 100644 --- a/ios/BlePlx.mm +++ b/ios/BlePlx.mm @@ -66,17 +66,19 @@ - (void)state:(RCTPromiseResolveBlock)resolve - (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()); + { + 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]; } diff --git a/ios/CentralManagerDelegate.swift b/ios/CentralManagerDelegate.swift index f996680d..f950fc31 100644 --- a/ios/CentralManagerDelegate.swift +++ b/ios/CentralManagerDelegate.swift @@ -29,6 +29,9 @@ final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate, Sendable /// 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 } @@ -58,6 +61,9 @@ final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate, Sendable 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, @@ -66,6 +72,16 @@ final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate, Sendable 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() @@ -185,6 +201,12 @@ final class LockedBox: @unchecked Sendable { 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 @@ -200,6 +222,10 @@ final class LockedDictionary: @unchecked Sendable { 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 { @@ -208,4 +234,8 @@ final class LockedDictionary: @unchecked Sendable { dict.removeAll() } } + + func removeAllValues() { + lock.withLock { dict.removeAll() } + } } diff --git a/ios/PeripheralDelegate.swift b/ios/PeripheralDelegate.swift index 1dba787e..74022a6e 100644 --- a/ios/PeripheralDelegate.swift +++ b/ios/PeripheralDelegate.swift @@ -60,6 +60,9 @@ final class PeripheralDelegate: NSObject, CBPeripheralDelegate, Sendable { // 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>() @@ -103,6 +106,10 @@ final class PeripheralDelegate: NSObject, CBPeripheralDelegate, Sendable { pendingRSSI.value = continuation } + func addL2CAPOpenContinuation(_ continuation: CheckedContinuation) { + pendingL2CAPOpen.value = continuation + } + // MARK: - CBPeripheralDelegate — Service Discovery func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { @@ -219,6 +226,33 @@ final class PeripheralDelegate: NSObject, CBPeripheralDelegate, Sendable { } } + // 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) { diff --git a/ios/PeripheralWrapper.swift b/ios/PeripheralWrapper.swift index 96836e15..9166d2c5 100644 --- a/ios/PeripheralWrapper.swift +++ b/ios/PeripheralWrapper.swift @@ -184,8 +184,19 @@ actor PeripheralWrapper { // MARK: - L2CAP @available(iOS 11.0, *) - func openL2CAPChannel(psm: CBL2CAPPSM) { - peripheral.openL2CAPChannel(psm) + 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 diff --git a/package.json b/package.json index 1f7cc454..dc83885d 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.0.0", + "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", diff --git a/src/BleManager.ts b/src/BleManager.ts index 3bbfd0d9..b26a01a6 100644 --- a/src/BleManager.ts +++ b/src/BleManager.ts @@ -1,7 +1,7 @@ import { TurboModuleRegistry, Platform } from 'react-native' import type { EventSubscription, TurboModule } from 'react-native' import type { State, ScanOptions, ConnectOptions, ConnectionPriority } from './types' -import { BleError } from './BleError' +import { BleError, BleErrorCode } from './BleError' import { EventBatcher } from './EventBatcher' // --------------------------------------------------------------------------- @@ -169,6 +169,8 @@ interface NativeBlePlxSpec extends TurboModule { 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 }> } // --------------------------------------------------------------------------- @@ -365,7 +367,8 @@ export class BleManager { }) // Start native scan (synchronous — errors come via onError) - this.nativeModule.startDeviceScan(serviceUuids, options ?? null) + // Pass empty object instead of null — iOS Codegen struct crashes on null backing dictionary + this.nativeModule.startDeviceScan(serviceUuids, options ?? {}) } async stopDeviceScan(): Promise { @@ -385,7 +388,8 @@ export class BleManager { // ----------------------------------------------------------------------- async connectToDevice(deviceId: string, options?: ConnectOptions | null): Promise { - return this.nativeModule.connectToDevice(deviceId, options ?? null) + // 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 { @@ -596,6 +600,54 @@ export class BleManager { 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 // ----------------------------------------------------------------------- diff --git a/src/index.ts b/src/index.ts index c3bb8eaa..d411c226 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,18 @@ export { BleManager } from './BleManager' export type { + BleManagerOptions, Subscription, MonitorOptions, DeviceInfo, CharacteristicInfo, ScanResult, ConnectionStateEvent, - CharacteristicValueEvent + CharacteristicValueEvent, + BondStateEvent, + ConnectionEvent, + RestoreStateEvent, + L2CAPChannelEvent, + PhyInfo } from './BleManager' export { Device } from './Device' export { Service } from './Service' 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 00000000..db956cae --- /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 00000000..eb878970 --- /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 00000000..13613e3e --- /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 00000000..73c00596 --- /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 00000000..d99b59fe --- /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 00000000..ed243f08 --- /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 00000000..f65d6753 --- /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 00000000..f3704acd --- /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 00000000..1f5a66cf --- /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 00000000..ea04f002 --- /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 00000000..2b487e3e --- /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 00000000..ab116d13 --- /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 00000000..e3e570a3 --- /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 00000000..5bac8ac5 --- /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 0000000000000000000000000000000000000000..8bdaf60c75ab801e22807dde59e12a8735a34077 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 0 HcmV?d00001 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 2ea3535d..2733ed5d 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 1aa94a42..ef07e016 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 00000000..efb0db39 --- /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 2a21c289..f3351022 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/yarn.lock b/yarn.lock index 6f6bc6bb..f4fc3ef3 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" From 7b29d326024ac29d78074d35c139a7a9a7102694 Mon Sep 17 00:00:00 2001 From: Shannon Hicks Date: Mon, 16 Mar 2026 18:02:48 -0500 Subject: [PATCH 36/36] fix: eliminate all explicit-any lint warnings in unit tests --- __tests__/BleManager.test.ts | 95 +++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/__tests__/BleManager.test.ts b/__tests__/BleManager.test.ts index e7146596..dfcc76a2 100644 --- a/__tests__/BleManager.test.ts +++ b/__tests__/BleManager.test.ts @@ -1,18 +1,47 @@ // __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: any) => void> = [] -let connectionStateHandlers: Array<(event: any) => void> = [] -let charValueHandlers: Array<(event: any) => void> = [] -let stateChangeHandler: ((event: any) => void) | null = null -let restoreStateHandler: ((event: any) => void) | null = null -let bondStateChangeHandler: ((event: any) => void) | null = null -let connectionEventHandler: ((event: any) => void) | null = null -let l2capDataHandler: ((event: any) => void) | null = null -let l2capCloseHandler: ((event: any) => void) | null = null +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 @@ -239,7 +268,7 @@ describe('BleManager', () => { // ------------------------------------------------------------------------- test('startDeviceScan calls native and routes scan results immediately with batchInterval 0', async () => { - const received: any[] = [] + const received: ScanResult[] = [] manager.startDeviceScan(null, null, (_err, device) => { if (device) received.push(device) }) @@ -260,7 +289,7 @@ describe('BleManager', () => { // ------------------------------------------------------------------------- test('stopDeviceScan cleans up batcher and subscription', async () => { - const received: any[] = [] + const received: ScanResult[] = [] manager.startDeviceScan(null, null, (_err, device) => { if (device) received.push(device) }) @@ -282,7 +311,7 @@ describe('BleManager', () => { 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: any[] = [] + const received: ScanResult[] = [] batchedManager.startDeviceScan(null, null, (_err, device) => { if (device) received.push(device) }) @@ -305,7 +334,7 @@ describe('BleManager', () => { test('monitorCharacteristicForDevice routes filtered events', async () => { await manager.createClient() - const received: any[] = [] + const received: CharacteristicValueEvent[] = [] const sub = manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', (_err, event) => { if (event) received.push(event) }) @@ -313,7 +342,9 @@ describe('BleManager', () => { expect(mockNativeModule.monitorCharacteristic).toHaveBeenCalled() expect(mockNativeModule.onCharacteristicValueUpdate).toHaveBeenCalled() - const txId = (mockNativeModule.monitorCharacteristic.mock.calls[0] as any[])[4] + 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]! @@ -340,7 +371,9 @@ describe('BleManager', () => { const sub = manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char', jest.fn()) - const txId = (mockNativeModule.monitorCharacteristic.mock.calls[0] as any[])[4] + const txId = ( + mockNativeModule.monitorCharacteristic.mock.calls[0] as [string, string, string, string | null, string | null] + )[4] sub.remove() @@ -422,12 +455,12 @@ describe('BleManager', () => { }) expect(callback).toHaveBeenCalledTimes(1) - const [err, device] = callback.mock.calls[0] as [BleError, any] + 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') + 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(err!.platform).toBe('ios') expect(device).not.toBeNull() }) @@ -501,7 +534,13 @@ describe('BleManager', () => { manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char1', jest.fn()) manager.monitorCharacteristicForDevice('AA:BB', 'svc', 'char2', jest.fn()) - const calls = mockNativeModule.monitorCharacteristic.mock.calls as any[][] + 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 @@ -533,8 +572,12 @@ describe('BleManager', () => { 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 any[])[4] - const txId2 = (mockNativeModule.monitorCharacteristic.mock.calls[1] as any[])[4] + 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()) @@ -597,7 +640,13 @@ describe('BleManager', () => { subscriptionType: 'indicate' }) - const call = mockNativeModule.monitorCharacteristic.mock.calls[0] as any[] + const call = mockNativeModule.monitorCharacteristic.mock.calls[0] as [ + string, + string, + string, + string | null, + string | null + ] expect(call[3]).toBe('indicate') })

OG^bt6yG2??7FhdGVc8feK}syBta)V!iG4 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 UD9cJ(z+Vc8``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBevYS$V@L(#+o>0M8x(k4{Kcp9UJ#tKM)?F= z$9w_a3sO!C^mpbP0 Hl+XkKM72er literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..21b9bd26634ab84d284f737916b14294d4f267b9 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^h9Jzr1|*B;ILZJi&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=5- zQFX(-2W+y8<_;bikNB$vUtRS6z3)j~B9nfv?E(hDtFMxOMmX*~W;4A@Fo54+s(WMd z!F>OW=d7xHx%1l27Yj~Y^d)T9v0aOO=YOcwYqx)SVViwTc8#o9l<|S|39sayGO#F{ zdT>v*D&x1-yY#?O!n=OMv}j(>TWxcmvn>^1+;(Q&))eLpG3SVb0$~O%qCAPL&Jl{O z0lR;%R9NTA^|3|vQPdIv{u_nPOG~DCxF6o?U#cwjYC~gpQ>XK9?b(cFe$!lLUv0ks ort5xy{bO_M7q+bIO1HL~uZ{0AeB!2k8R&NgPgg&ebxsLQ02VNSvj6}9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..422202d5e195f089679236b712492f760dd7b2a0 GIT binary patch literal 468 zcmeAS@N?(olHy`uVBq!ia0vp^o*>M@1|$oMx$gie&H|6fVg?393lL^>oo1K-6l5$8 za(7}_cTVOdki(Mh=r zn=rYIIf2D2p?M1LF2_xbcNK1M@pA-JuP7{7^MCTXUys^lW?=ZxJf&|p zZ|Ucc(!1ZerAnHV-nRDLdnwE~%c)dry}ni0-n3m4wWr>+__yp}jOJpu(|j+A1A2{K zs`gF|S8VYm|AUjgvb%-=}xl&cjPCcm7{+=`Ka#8 uci0PO-9C_+=VNi!R@kqYnV~^yFLV2`1K(!Oea;Px1O`u6KbLh*2~7Y5`MPib literal 0 HcmV?d00001 diff --git a/example-expo/assets/images/tabIcons/home.png b/example-expo/assets/images/tabIcons/home.png new file mode 100644 index 0000000000000000000000000000000000000000..ad5699c4295db84c342833358bbc47c159882f45 GIT binary patch literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#NuDl_Ar*{or)=bHP!Mq4zpFvnqD}ecOOCvT zz6UAW%62u%aSA!}Jo%vEQ*qK;qwn_?hE544$rjF6*2UngGy!S!DnK literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..22a1f2c7442f3ee07b8af0bb511652c7931e4888 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=Fu!ex z5R76h+n^f3q$jvY@m@o*1%{KjjaD#jV1FNcwC#?fA9M7dIi>x*i6% literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f5d1f9a41eef3a1a3581d3c8a9d4b2a2116e6c00 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!oCO|{#S9FJ79h;%I?XTvD9BhG z1|_0K&Q$GmBFpBb}>&BetT1TD~`LDWlGPWux)c4$mb4ymuxN~c= zR$0+f-&H^br{;tRPTuW$b8?d9ne!7`7rranH$CM<0)qjYXMgKmW{Kvd&4*2E-U*y< zey)`7c(d+C*p*7XPybdMDyjYYb1y$&bBMsgCJs@T6$(zQf?6E`jxAhD#0l+TeJgnW zT=B&13(Svf&Tx!A^0}+^>x$QZ&gV-`XmoV{pdtIH#NbM0+{WXr{`dT!i*5f=W4!Z9 mP}>x73;)TT9YC<7mf>_~gnIu4&ojXIVDNPHb6Mw<&;$TZ55|K4 literal 0 HcmV?d00001 diff --git a/example-expo/assets/images/tutorial-web.png b/example-expo/assets/images/tutorial-web.png new file mode 100644 index 0000000000000000000000000000000000000000..e4a8c58f7c433af31a0549fbb3f53eb0d528e682 GIT binary patch literal 58959 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 z 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 literal 0 HcmV?d00001 diff --git a/example-expo/assets/splash.png b/example-expo/assets/splash.png deleted file mode 100644 index 0e89705a9436743e42954d3744a0e7ff0d3d4701..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47346 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 diff --git a/example-expo/babel.config.js b/example-expo/babel.config.js deleted file mode 100644 index d799d652..00000000 --- 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 8beb3443..00000000 --- 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 3d5782c7..00000000 --- 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 01e4f5bf..00000000 --- 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 d24d4358..00000000 --- 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 de9f7b75..00000000 --- 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 50e47967..00000000 --- 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 68699179..00000000 --- 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 d7f47d82..00000000 --- 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 1658a437..00000000 --- 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 b27f8328..00000000 --- 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 2732229faf0f150332a6f03c098d0bae8dc69ffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59468 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%0DnhQoc3A{aA*8p`p*}3)e{2L1y^pPctf^pY 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 UqrQf_p8*IwUHx3vIVCg!0A;QSnE(I) literal 0 HcmV?d00001 diff --git a/example-expo/assets/images/react-logo.png b/example-expo/assets/images/react-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9d72a9ffcbb39d89709073e1a7edd8ba414932c1 GIT binary patch literal 6341 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