unified-ble-manager 4.0.5 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -1
- package/MIGRATION_4.0.md +1 -1
- package/README.md +1 -1
- package/RELEASE.md +55 -16
- package/SBOM.cdx.json +4 -4
- package/THIRD_PARTY_LICENSES.json +1 -1
- package/android/src/main/java/com/sfourdrinier/unifiedblemanager/expo/UnifiedBleExpoRuntimeModule.java +18 -2
- package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt +8 -7
- package/android/src/main/jni/UnifiedBleProtocolJsiBinding.cpp +66 -0
- package/android/src/test/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidFindingsTest.kt +17 -0
- package/docs/ADR/2026-07-4.0-backend-contract.md +16 -4
- package/docs/BONDING.md +144 -7
- package/docs/ELECTRON.md +1 -1
- package/docs/EXPO_PLUGIN.md +2 -2
- package/docs/GAPS.4.0.md +1 -1
- package/docs/GETTING_STARTED.md +4 -4
- package/docs/NODE.md +2 -2
- package/docs/PLATFORMS.md +1 -1
- package/docs/UNIFIED_SEMANTICS.md +40 -5
- package/docs/WEB.md +1 -1
- package/docs/generated/PLATFORM_SUPPORT.md +1 -1
- package/lib/commonjs/backend-contract/capabilities.js +11 -0
- package/lib/commonjs/backend-contract/capabilities.js.map +1 -1
- package/lib/commonjs/backend-contract/ipc.js +7 -0
- package/lib/commonjs/backend-contract/ipc.js.map +1 -1
- package/lib/commonjs/backend-contract/scan-planning.js +10 -0
- package/lib/commonjs/backend-contract/scan-planning.js.map +1 -1
- package/lib/commonjs/backend-contract/security.js +54 -0
- package/lib/commonjs/backend-contract/security.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-backend-provider.js +3 -2
- package/lib/commonjs/backends/bluez/bluez-backend-provider.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-backend-runtime.js +1 -0
- package/lib/commonjs/backends/bluez/bluez-backend-runtime.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-backend.js +32 -20
- package/lib/commonjs/backends/bluez/bluez-backend.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-connection-capabilities.js +110 -0
- package/lib/commonjs/backends/bluez/bluez-connection-capabilities.js.map +1 -0
- package/lib/commonjs/backends/bluez/bluez-dbus-next-boundary.js +179 -0
- package/lib/commonjs/backends/bluez/bluez-dbus-next-boundary.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-operation-dispatcher.js +30 -20
- package/lib/commonjs/backends/bluez/bluez-operation-dispatcher.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-pairing-generation-capability.js +92 -0
- package/lib/commonjs/backends/bluez/bluez-pairing-generation-capability.js.map +1 -0
- package/lib/commonjs/backends/bluez/bluez-pairing-generation.js +193 -0
- package/lib/commonjs/backends/bluez/bluez-pairing-generation.js.map +1 -0
- package/lib/commonjs/backends/bluez/bluez-property-waiters.js +10 -0
- package/lib/commonjs/backends/bluez/bluez-property-waiters.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-security.js +97 -8
- package/lib/commonjs/backends/bluez/bluez-security.js.map +1 -1
- package/lib/commonjs/backends/corebluetooth/corebluetooth-cleanup.js +8 -0
- package/lib/commonjs/backends/corebluetooth/corebluetooth-cleanup.js.map +1 -1
- package/lib/commonjs/backends/corebluetooth/corebluetooth-connection-controls.js +12 -0
- package/lib/commonjs/backends/corebluetooth/corebluetooth-connection-controls.js.map +1 -1
- package/lib/commonjs/backends/reactnative/react-native-android-security.js +36 -4
- package/lib/commonjs/backends/reactnative/react-native-android-security.js.map +1 -1
- package/lib/commonjs/backends/reactnative/react-native-restoration.js +8 -0
- package/lib/commonjs/backends/reactnative/react-native-restoration.js.map +1 -1
- package/lib/commonjs/backends/winrt/winrt-backend-helpers.js +9 -0
- package/lib/commonjs/backends/winrt/winrt-backend-helpers.js.map +1 -1
- package/lib/commonjs/backends/winrt/winrt-security.js +23 -10
- package/lib/commonjs/backends/winrt/winrt-security.js.map +1 -1
- package/lib/commonjs/cli-json.js +7 -0
- package/lib/commonjs/cli-json.js.map +1 -1
- package/lib/commonjs/core/core-characteristic-operations.js +13 -0
- package/lib/commonjs/core/core-characteristic-operations.js.map +1 -1
- package/lib/commonjs/core/core-gatt-handles.js +10 -0
- package/lib/commonjs/core/core-gatt-handles.js.map +1 -1
- package/lib/commonjs/core/operation-coordinator.js +10 -1
- package/lib/commonjs/core/operation-coordinator.js.map +1 -1
- package/lib/commonjs/core/unified-ble-core.js +9 -0
- package/lib/commonjs/core/unified-ble-core.js.map +1 -1
- package/lib/commonjs/diagnostics/trace-format.js +12 -0
- package/lib/commonjs/diagnostics/trace-format.js.map +1 -1
- package/lib/commonjs/electron/connection-event-stream-registry.js +8 -0
- package/lib/commonjs/electron/connection-event-stream-registry.js.map +1 -1
- package/lib/commonjs/electron/main-binding.js +16 -0
- package/lib/commonjs/electron/main-binding.js.map +1 -1
- package/lib/commonjs/electron/main-router.js +11 -0
- package/lib/commonjs/electron/main-router.js.map +1 -1
- package/lib/commonjs/electron/renderer-stream-registry.js +8 -0
- package/lib/commonjs/electron/renderer-stream-registry.js.map +1 -1
- package/lib/commonjs/electron/renderer.js +11 -0
- package/lib/commonjs/electron/renderer.js.map +1 -1
- package/lib/commonjs/expo.js +20 -5
- package/lib/commonjs/expo.js.map +1 -1
- package/lib/commonjs/implementation-version.js +1 -1
- package/lib/commonjs/ipc/manager.js +18 -0
- package/lib/commonjs/ipc/manager.js.map +1 -1
- package/lib/commonjs/ipc/public-manager.js +22 -5
- package/lib/commonjs/ipc/public-manager.js.map +1 -1
- package/lib/commonjs/native-protocol/rn-android-boundary.js +5 -1
- package/lib/commonjs/native-protocol/rn-android-boundary.js.map +1 -1
- package/lib/commonjs/node-bluez.js +5 -2
- package/lib/commonjs/node-bluez.js.map +1 -1
- package/lib/commonjs/node-corebluetooth.js +11 -0
- package/lib/commonjs/node-corebluetooth.js.map +1 -1
- package/lib/commonjs/node-host-manager.js +14 -6
- package/lib/commonjs/node-host-manager.js.map +1 -1
- package/lib/commonjs/public/ble-manager.js +81 -14
- package/lib/commonjs/public/ble-manager.js.map +1 -1
- package/lib/commonjs/public/host-identity.js.map +1 -1
- package/lib/commonjs/public/scan-state-budget.js +11 -0
- package/lib/commonjs/public/scan-state-budget.js.map +1 -1
- package/lib/commonjs/public/security.js +17 -1
- package/lib/commonjs/public/security.js.map +1 -1
- package/lib/commonjs/public/stream-capacity.js +13 -2
- package/lib/commonjs/public/stream-capacity.js.map +1 -1
- package/lib/commonjs/tck/first-party/bluez-tck-registration.js +14 -2
- package/lib/commonjs/tck/first-party/bluez-tck-registration.js.map +1 -1
- package/lib/commonjs/testing/deterministic/deterministic-security.js +16 -3
- package/lib/commonjs/testing/deterministic/deterministic-security.js.map +1 -1
- package/lib/module/backend-contract/capabilities.js +11 -0
- package/lib/module/backend-contract/capabilities.js.map +1 -1
- package/lib/module/backend-contract/ipc.js +7 -0
- package/lib/module/backend-contract/ipc.js.map +1 -1
- package/lib/module/backend-contract/scan-planning.js +10 -0
- package/lib/module/backend-contract/scan-planning.js.map +1 -1
- package/lib/module/backend-contract/security.js +53 -1
- package/lib/module/backend-contract/security.js.map +1 -1
- package/lib/module/backends/bluez/bluez-backend-provider.js +3 -2
- package/lib/module/backends/bluez/bluez-backend-provider.js.map +1 -1
- package/lib/module/backends/bluez/bluez-backend-runtime.js +1 -0
- package/lib/module/backends/bluez/bluez-backend-runtime.js.map +1 -1
- package/lib/module/backends/bluez/bluez-backend.js +32 -20
- package/lib/module/backends/bluez/bluez-backend.js.map +1 -1
- package/lib/module/backends/bluez/bluez-connection-capabilities.js +105 -0
- package/lib/module/backends/bluez/bluez-connection-capabilities.js.map +1 -0
- package/lib/module/backends/bluez/bluez-dbus-next-boundary.js +179 -0
- package/lib/module/backends/bluez/bluez-dbus-next-boundary.js.map +1 -1
- package/lib/module/backends/bluez/bluez-operation-dispatcher.js +29 -20
- package/lib/module/backends/bluez/bluez-operation-dispatcher.js.map +1 -1
- package/lib/module/backends/bluez/bluez-pairing-generation-capability.js +87 -0
- package/lib/module/backends/bluez/bluez-pairing-generation-capability.js.map +1 -0
- package/lib/module/backends/bluez/bluez-pairing-generation.js +188 -0
- package/lib/module/backends/bluez/bluez-pairing-generation.js.map +1 -0
- package/lib/module/backends/bluez/bluez-property-waiters.js +10 -0
- package/lib/module/backends/bluez/bluez-property-waiters.js.map +1 -1
- package/lib/module/backends/bluez/bluez-security.js +97 -8
- package/lib/module/backends/bluez/bluez-security.js.map +1 -1
- package/lib/module/backends/corebluetooth/corebluetooth-cleanup.js +9 -0
- package/lib/module/backends/corebluetooth/corebluetooth-cleanup.js.map +1 -1
- package/lib/module/backends/corebluetooth/corebluetooth-connection-controls.js +13 -0
- package/lib/module/backends/corebluetooth/corebluetooth-connection-controls.js.map +1 -1
- package/lib/module/backends/reactnative/react-native-android-security.js +36 -4
- package/lib/module/backends/reactnative/react-native-android-security.js.map +1 -1
- package/lib/module/backends/reactnative/react-native-restoration.js +8 -0
- package/lib/module/backends/reactnative/react-native-restoration.js.map +1 -1
- package/lib/module/backends/winrt/winrt-backend-helpers.js +10 -0
- package/lib/module/backends/winrt/winrt-backend-helpers.js.map +1 -1
- package/lib/module/backends/winrt/winrt-security.js +23 -10
- package/lib/module/backends/winrt/winrt-security.js.map +1 -1
- package/lib/module/cli-json.js +7 -0
- package/lib/module/cli-json.js.map +1 -1
- package/lib/module/core/core-characteristic-operations.js +13 -0
- package/lib/module/core/core-characteristic-operations.js.map +1 -1
- package/lib/module/core/core-gatt-handles.js +10 -0
- package/lib/module/core/core-gatt-handles.js.map +1 -1
- package/lib/module/core/operation-coordinator.js +10 -1
- package/lib/module/core/operation-coordinator.js.map +1 -1
- package/lib/module/core/unified-ble-core.js +9 -0
- package/lib/module/core/unified-ble-core.js.map +1 -1
- package/lib/module/diagnostics/trace-format.js +12 -0
- package/lib/module/diagnostics/trace-format.js.map +1 -1
- package/lib/module/electron/connection-event-stream-registry.js +8 -0
- package/lib/module/electron/connection-event-stream-registry.js.map +1 -1
- package/lib/module/electron/main-binding.js +16 -0
- package/lib/module/electron/main-binding.js.map +1 -1
- package/lib/module/electron/main-router.js +11 -0
- package/lib/module/electron/main-router.js.map +1 -1
- package/lib/module/electron/renderer-stream-registry.js +8 -0
- package/lib/module/electron/renderer-stream-registry.js.map +1 -1
- package/lib/module/electron/renderer.js +11 -0
- package/lib/module/electron/renderer.js.map +1 -1
- package/lib/module/expo.js +20 -5
- package/lib/module/expo.js.map +1 -1
- package/lib/module/implementation-version.js +1 -1
- package/lib/module/ipc/manager.js +18 -0
- package/lib/module/ipc/manager.js.map +1 -1
- package/lib/module/ipc/public-manager.js +23 -6
- package/lib/module/ipc/public-manager.js.map +1 -1
- package/lib/module/native-protocol/rn-android-boundary.js +5 -1
- package/lib/module/native-protocol/rn-android-boundary.js.map +1 -1
- package/lib/module/node-bluez.js +5 -2
- package/lib/module/node-bluez.js.map +1 -1
- package/lib/module/node-corebluetooth.js +11 -0
- package/lib/module/node-corebluetooth.js.map +1 -1
- package/lib/module/node-host-manager.js +14 -6
- package/lib/module/node-host-manager.js.map +1 -1
- package/lib/module/public/ble-manager.js +80 -13
- package/lib/module/public/ble-manager.js.map +1 -1
- package/lib/module/public/host-identity.js.map +1 -1
- package/lib/module/public/scan-state-budget.js +11 -0
- package/lib/module/public/scan-state-budget.js.map +1 -1
- package/lib/module/public/security.js +17 -1
- package/lib/module/public/security.js.map +1 -1
- package/lib/module/public/stream-capacity.js +13 -2
- package/lib/module/public/stream-capacity.js.map +1 -1
- package/lib/module/tck/first-party/bluez-tck-registration.js +14 -2
- package/lib/module/tck/first-party/bluez-tck-registration.js.map +1 -1
- package/lib/module/testing/deterministic/deterministic-security.js +16 -3
- package/lib/module/testing/deterministic/deterministic-security.js.map +1 -1
- package/lib/typescript/commonjs/src/backend-contract/capabilities.d.ts +11 -0
- package/lib/typescript/commonjs/src/backend-contract/capabilities.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backend-contract/ipc.d.ts +7 -0
- package/lib/typescript/commonjs/src/backend-contract/ipc.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backend-contract/scan-planning.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backend-contract/security.d.ts +45 -1
- package/lib/typescript/commonjs/src/backend-contract/security.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts +13 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-runtime.d.ts +8 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-runtime.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend.d.ts +5 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-connection-capabilities.d.ts +13 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-connection-capabilities.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-dbus-contract.d.ts +2 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-dbus-contract.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-dbus-next-boundary.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-operation-dispatcher.d.ts +12 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-operation-dispatcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation-capability.d.ts +16 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation-capability.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation.d.ts +41 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-property-waiters.d.ts +10 -0
- package/lib/typescript/commonjs/src/backends/bluez/bluez-property-waiters.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-security.d.ts +0 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-security.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-cleanup.d.ts +8 -0
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-cleanup.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-connection-controls.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-security.d.ts +5 -0
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-security.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-restoration.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/winrt/winrt-backend-helpers.d.ts +9 -0
- package/lib/typescript/commonjs/src/backends/winrt/winrt-backend-helpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/winrt/winrt-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/winrt/winrt-security.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/cli-json.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/core-characteristic-operations.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/core-gatt-handles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/operation-coordinator.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/unified-ble-core.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/diagnostics/trace-format.d.ts +12 -0
- package/lib/typescript/commonjs/src/diagnostics/trace-format.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/connection-event-stream-registry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/main-binding.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/main-router.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/renderer-stream-registry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/renderer.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/implementation-version.d.ts +1 -1
- package/lib/typescript/commonjs/src/ipc/manager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/ipc/public-manager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/node-bluez.d.ts +14 -0
- package/lib/typescript/commonjs/src/node-bluez.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/node-corebluetooth.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/public/ble-manager.d.ts +38 -1
- package/lib/typescript/commonjs/src/public/ble-manager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/public/host-identity.d.ts +7 -1
- package/lib/typescript/commonjs/src/public/host-identity.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/public/scan-state-budget.d.ts +11 -0
- package/lib/typescript/commonjs/src/public/scan-state-budget.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/public/security.d.ts +6 -0
- package/lib/typescript/commonjs/src/public/security.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/public/stream-capacity.d.ts +13 -2
- package/lib/typescript/commonjs/src/public/stream-capacity.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/tck/first-party/bluez-tck-registration.d.ts +1 -1
- package/lib/typescript/commonjs/src/tck/first-party/bluez-tck-registration.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/testing/deterministic/deterministic-security.d.ts.map +1 -1
- package/lib/typescript/module/src/backend-contract/capabilities.d.ts +11 -0
- package/lib/typescript/module/src/backend-contract/capabilities.d.ts.map +1 -1
- package/lib/typescript/module/src/backend-contract/ipc.d.ts +7 -0
- package/lib/typescript/module/src/backend-contract/ipc.d.ts.map +1 -1
- package/lib/typescript/module/src/backend-contract/scan-planning.d.ts.map +1 -1
- package/lib/typescript/module/src/backend-contract/security.d.ts +45 -1
- package/lib/typescript/module/src/backend-contract/security.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts +13 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend-runtime.d.ts +8 -0
- package/lib/typescript/module/src/backends/bluez/bluez-backend-runtime.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend.d.ts +5 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-connection-capabilities.d.ts +13 -0
- package/lib/typescript/module/src/backends/bluez/bluez-connection-capabilities.d.ts.map +1 -0
- package/lib/typescript/module/src/backends/bluez/bluez-dbus-contract.d.ts +2 -0
- package/lib/typescript/module/src/backends/bluez/bluez-dbus-contract.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-dbus-next-boundary.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-operation-dispatcher.d.ts +12 -0
- package/lib/typescript/module/src/backends/bluez/bluez-operation-dispatcher.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation-capability.d.ts +16 -0
- package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation-capability.d.ts.map +1 -0
- package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation.d.ts +41 -0
- package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation.d.ts.map +1 -0
- package/lib/typescript/module/src/backends/bluez/bluez-property-waiters.d.ts +10 -0
- package/lib/typescript/module/src/backends/bluez/bluez-property-waiters.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-security.d.ts +0 -1
- package/lib/typescript/module/src/backends/bluez/bluez-security.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-cleanup.d.ts +8 -0
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-cleanup.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-connection-controls.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-android-security.d.ts +5 -0
- package/lib/typescript/module/src/backends/reactnative/react-native-android-security.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-restoration.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/winrt/winrt-backend-helpers.d.ts +9 -0
- package/lib/typescript/module/src/backends/winrt/winrt-backend-helpers.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/winrt/winrt-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/winrt/winrt-security.d.ts.map +1 -1
- package/lib/typescript/module/src/cli-json.d.ts.map +1 -1
- package/lib/typescript/module/src/core/core-characteristic-operations.d.ts.map +1 -1
- package/lib/typescript/module/src/core/core-gatt-handles.d.ts.map +1 -1
- package/lib/typescript/module/src/core/operation-coordinator.d.ts.map +1 -1
- package/lib/typescript/module/src/core/unified-ble-core.d.ts.map +1 -1
- package/lib/typescript/module/src/diagnostics/trace-format.d.ts +12 -0
- package/lib/typescript/module/src/diagnostics/trace-format.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/connection-event-stream-registry.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/main-binding.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/main-router.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/renderer-stream-registry.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/renderer.d.ts.map +1 -1
- package/lib/typescript/module/src/expo.d.ts.map +1 -1
- package/lib/typescript/module/src/implementation-version.d.ts +1 -1
- package/lib/typescript/module/src/ipc/manager.d.ts.map +1 -1
- package/lib/typescript/module/src/ipc/public-manager.d.ts.map +1 -1
- package/lib/typescript/module/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
- package/lib/typescript/module/src/node-bluez.d.ts +14 -0
- package/lib/typescript/module/src/node-bluez.d.ts.map +1 -1
- package/lib/typescript/module/src/node-corebluetooth.d.ts.map +1 -1
- package/lib/typescript/module/src/public/ble-manager.d.ts +38 -1
- package/lib/typescript/module/src/public/ble-manager.d.ts.map +1 -1
- package/lib/typescript/module/src/public/host-identity.d.ts +7 -1
- package/lib/typescript/module/src/public/host-identity.d.ts.map +1 -1
- package/lib/typescript/module/src/public/scan-state-budget.d.ts +11 -0
- package/lib/typescript/module/src/public/scan-state-budget.d.ts.map +1 -1
- package/lib/typescript/module/src/public/security.d.ts +6 -0
- package/lib/typescript/module/src/public/security.d.ts.map +1 -1
- package/lib/typescript/module/src/public/stream-capacity.d.ts +13 -2
- package/lib/typescript/module/src/public/stream-capacity.d.ts.map +1 -1
- package/lib/typescript/module/src/tck/first-party/bluez-tck-registration.d.ts +1 -1
- package/lib/typescript/module/src/tck/first-party/bluez-tck-registration.d.ts.map +1 -1
- package/lib/typescript/module/src/testing/deterministic/deterministic-security.d.ts.map +1 -1
- package/native/PREBUILDS.json +5 -5
- package/native/electron/corebluetooth/prebuilds/darwin-arm64/unified_ble_corebluetooth.node +0 -0
- package/native/electron/corebluetooth/prebuilds/darwin-x64/unified_ble_corebluetooth.node +0 -0
- package/native/electron/winrt/prebuilds/win32-arm64/unified_ble_winrt.node +0 -0
- package/native/electron/winrt/prebuilds/win32-x64/unified_ble_winrt.node +0 -0
- package/native/protocol/src/NativeProtocolV2Codec.cpp +48 -4
- package/native/tauri/Cargo.toml +14 -0
- package/native/tauri/src/btleplug_dispatcher.rs +593 -26
- package/native/tauri/src/capabilities.rs +2 -1
- package/package.json +1 -1
- package/src/backend-contract/capabilities.ts +11 -0
- package/src/backend-contract/ipc.ts +7 -0
- package/src/backend-contract/scan-planning.ts +9 -0
- package/src/backend-contract/security.ts +58 -1
- package/src/backends/bluez/bluez-backend-provider.ts +15 -2
- package/src/backends/bluez/bluez-backend-runtime.ts +9 -0
- package/src/backends/bluez/bluez-backend.ts +46 -26
- package/src/backends/bluez/bluez-connection-capabilities.ts +144 -0
- package/src/backends/bluez/bluez-dbus-contract.ts +2 -0
- package/src/backends/bluez/bluez-dbus-next-boundary.ts +162 -0
- package/src/backends/bluez/bluez-operation-dispatcher.ts +39 -34
- package/src/backends/bluez/bluez-pairing-generation-capability.ts +100 -0
- package/src/backends/bluez/bluez-pairing-generation.ts +220 -0
- package/src/backends/bluez/bluez-property-waiters.ts +10 -0
- package/src/backends/bluez/bluez-security.ts +134 -22
- package/src/backends/corebluetooth/corebluetooth-cleanup.ts +8 -0
- package/src/backends/corebluetooth/corebluetooth-connection-controls.ts +12 -0
- package/src/backends/reactnative/react-native-android-security.ts +34 -2
- package/src/backends/reactnative/react-native-restoration.ts +8 -0
- package/src/backends/winrt/winrt-backend-helpers.ts +9 -0
- package/src/backends/winrt/winrt-security.ts +23 -3
- package/src/cli-json.ts +7 -0
- package/src/core/core-characteristic-operations.ts +13 -0
- package/src/core/core-gatt-handles.ts +10 -0
- package/src/core/operation-coordinator.ts +10 -1
- package/src/core/unified-ble-core.ts +9 -0
- package/src/diagnostics/trace-format.ts +12 -0
- package/src/electron/connection-event-stream-registry.ts +8 -0
- package/src/electron/main-binding.ts +16 -0
- package/src/electron/main-router.ts +11 -0
- package/src/electron/renderer-stream-registry.ts +8 -0
- package/src/electron/renderer.ts +11 -0
- package/src/expo.ts +20 -5
- package/src/implementation-version.ts +1 -1
- package/src/ipc/manager.ts +17 -0
- package/src/ipc/public-manager.ts +27 -5
- package/src/native-protocol/rn-android-boundary.ts +5 -1
- package/src/node-bluez.ts +21 -3
- package/src/node-corebluetooth.ts +11 -0
- package/src/node-host-manager.ts +14 -6
- package/src/public/ble-manager.ts +98 -15
- package/src/public/host-identity.ts +7 -1
- package/src/public/scan-state-budget.ts +11 -0
- package/src/public/security.ts +25 -1
- package/src/public/stream-capacity.ts +13 -2
- package/src/tck/first-party/bluez-tck-registration.ts +19 -3
- package/src/testing/deterministic/deterministic-security.ts +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `unified-ble-manager` are documented here.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [4.0.7] - 2026-08-26
|
|
6
|
+
|
|
7
|
+
### Changed behaviour
|
|
8
|
+
|
|
9
|
+
- `cancelPairing()` reports what the cancellation **achieved**, not what it requested, and can no longer contradict the pairing it cancelled. Every backend answered `'cancelled'` unconditionally the moment the cancel was dispatched, so a cancellation that lost the race told the caller no bond exists while one did — and a caller who believes that never looks again. It now reads the in-flight pairing's own result rather than forming a second opinion, which is what makes the two calls incapable of disagreeing: there is one source of truth and the cancellation reads it. `SecurityCancelPairingResult` gains the words to say so — `'paired'` when the bond completed before the cancellation arrived, and `'rejected'` (carrying the peer's reason) when the peer refused, because claiming credit for stopping something that stopped itself is the same substitution with the arrow reversed. A pairing that *fails* is not given an invented outcome: `cancelPairing()` rejects with the error the pairing rejected with. `'paired'` deliberately matches `SecurityPairResult`'s `'paired'` — a bond exists as a result of this operation — and is not `'already-paired'`, which in that type means the peer was bonded *before* the call; one word, one meaning, in both types. Applies to BlueZ, Android, WinRT **and** the deterministic `/testing` backend, since a mock that answers differently from every real radio lets a consumer's suite pass against a contract no device honours. WinRT additionally short-circuited *upstream* of the shared mapper — when its dispatcher reported the operation already terminal, which is exactly the lost race, it answered `'not-pairing'`, contradicting its own `pair()` and making that word mean both "there was nothing to stop" and "it was already over" (#159).
|
|
10
|
+
|
|
11
|
+
### Additions
|
|
12
|
+
|
|
13
|
+
- **`PairOptions.secureConnections` can now actually select the LE pairing generation on BlueZ — behind a host-supplied privileged operation.** Some peripherals accept only LE Legacy and terminate the link on a Secure Connections pairing request; 4.0.6 gave the contract a way to say so but no backend that could honour it. `org.bluez.Adapter1` (BlueZ 5.85) exposes no Secure Connections property and `Device1.Pair` takes no parameters — the setting lives behind the kernel management socket's Set Secure Connections command, which requires **`CAP_NET_ADMIN`**.
|
|
14
|
+
|
|
15
|
+
This package never acquires that privilege: it opens no management socket, shells out to nothing, and does not assume it is root. A library that silently escalates hands an application capabilities its author did not choose and cannot audit. The **host** supplies the operation via `BluezBackendProviderOptions.pairingGeneration`, which makes the escalation visible in the application that opted into it. Omit it and `'require'`/`'disallow'` keep failing closed exactly as in 4.0.6 — the default posture is unchanged.
|
|
16
|
+
|
|
17
|
+
Three properties of the kernel setting a host must accept, documented in `docs/BONDING.md` rather than discovered: it is **adapter-wide**, not per-pairing, so every pairing on that controller uses the selected generation while it is held; it **outlives the process**, because the kernel keeps it until something sets it back; and a **failed restore never changes the pairing's outcome** — a bond that was created is reported as created, with the restore failure reported separately, because leaving a controller in LE Legacy and telling a caller they are not bonded are both serious and are different facts. Concurrent *directed* pairings on one adapter are serialised so they cannot corrupt each other's restore value; an undirected (`'prefer'`) pairing does not take that lock and uses whichever generation is held, which `docs/BONDING.md` states beside the option.
|
|
18
|
+
|
|
19
|
+
`security:pairing-generation` joins the capability catalog and is reported by the instantiated backend at runtime: `unsupported` with the platform reason when no operation was supplied, `limited` when one was — so two BlueZ backends on one machine legitimately answer differently. Evidence is deterministic tests only; this is **not** physical-radio proof, and the label says so (#144).
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
- A Tauri connect that succeeds physically but is then denied admission no longer strands the peer. Both compensation branches — the caller vanished, and the caller's lease went stale — removed the peer reservation *before* attempting the disconnect and then discarded its result with `.ok()`. If that disconnect failed or hung while the peripheral stayed connected, the result was a connected peer with no owner and no handle: nothing could reach it, nothing could retry it, and the caller was told only that admission was denied. Compensation now follows the rule the explicit disconnect already follows — a bounded wait, a fresh state reading, and a D-Bus error naming a vanished device object read as evidence of release rather than as a failed question — and surrenders the reservation only when the platform proves the link is down. Genuine indeterminacy keeps it so a retry can reclaim the peer, and the admission error carries what compensation could not undo, which is the difference between "try again" and "a peer is stranded" (#146).
|
|
24
|
+
|
|
25
|
+
### Known limitations
|
|
26
|
+
|
|
27
|
+
- `pair()`'s abort path still reports `'cancelled'` without knowing whether the daemon bonded anyway. Learning the truth means waiting for the radio, and waiting risks a hang that a wedged daemon would inflict on the caller — which the suite explicitly forbids. Resolving it needs a vocabulary that can express "cancellation requested, bond state not yet known"; `state()` and `watch()` remain authoritative meanwhile (#157).
|
|
28
|
+
|
|
29
|
+
## [4.0.6] - 2026-08-26
|
|
30
|
+
|
|
31
|
+
The first release cut against a live peripheral on Android as well as Linux. Most of it is defects that only real hardware surfaced, several of them boundaries that discarded evidence and so presented a specific fault as silence. Does not retag `v4.0.5`.
|
|
32
|
+
|
|
33
|
+
### Fixes
|
|
34
|
+
|
|
35
|
+
- A Tauri disconnect that fails no longer leaves the peer owned when the platform can prove it was released. btleplug's CoreBluetooth `disconnect()` never resolves once the peripheral has left its internal map, so the call is bounded and the outcome decided by a fresh state reading: gone means released, still connected means the retry keeps ownership. On BlueZ the state reading is classified before it is stringified, because a removed D-Bus device object is not a failed question but the answer to it - previously that case was treated as unknowable and the peer stayed owned until the process restarted. Genuine indeterminacy - a D-Bus timeout, the daemon gone, the adapter pulled - still retains ownership (#145, #154).
|
|
36
|
+
|
|
37
|
+
- BlueZ no longer reports a pairing it could not stop as `cancelled`. `Device1.CancelPairing` rejections were all swallowed, so a refusal from bluetoothd — `org.bluez.Error.Failed`, `NotAuthorized`, a D-Bus timeout, the daemon gone — left the in-flight `Pair` running while `cancelPairing()` returned `cancelled` and the abort/deadline path did the same. A caller told no bond exists while one is still being made cannot recover, because it never learns to look. Only the two rejections that prove nothing is left running are treated as success — `org.bluez.Error.DoesNotExist` and `org.freedesktop.DBus.Error.UnknownObject`, an answer to the question rather than a failure to answer it, the same pair the Tauri disconnect path classifies for the same reason. Anything else now surfaces as `platform.failure` carrying the D-Bus error name (#143).
|
|
38
|
+
|
|
39
|
+
- `scan()` works on React Native Android again, and so does every other control-plane operation there. The Android dispatcher stamped a hardcoded `1` on field 1 of every result and event it built while the native codec has required version 2 since the schema was frozen, so all of them — `scanStarted`, a connect completing, a connect failing, a cancellation, a bond state change, a lost link — were rejected at the JSI boundary as version-incompatible and dropped. A scan therefore never received its terminal: the radio discovered the peripheral 38 times in one four-minute capture and the application was handed nothing. The stamp now comes from the generated `NATIVE_PROTOCOL_VERSION`, so it cannot drift from the version the codec validates against (#140).
|
|
40
|
+
- A record the codec refuses is no longer only written to logcat: the Android binding counts quarantined records per attachment and emits a `recordQuarantined` diagnostic in place of the record it could not deliver, so an application learns that the boundary discarded something it was asked to deliver (#140).
|
|
41
|
+
- A version-incompatible record names its kind, the version it carried and the version expected, so a quarantined record identifies its emitter instead of leaving four record kinds to choose between (#140).
|
|
42
|
+
- `adapter.waitUntilReady()` no longer reports a radio that is still starting up as one the caller is not permitted to use. The React Native Android boundary answers `authorization: 'unavailable'` until the radio publishes its first authoritative state, and the readiness gate maps that value to `permission.denied` — so the one API whose purpose is to wait that window out failed on the very state it exists to wait for, on a phone with Bluetooth switched on and all four runtime permissions granted. The pending snapshot now reports `authorization: 'unknown'`, the value its `availability` and `power` siblings already carried for the same reason: the absence of a measurement is never a denial, so no readiness gate may block on it (#156).
|
|
43
|
+
- BlueZ states its connection-control truth instead of omitting the concept: `connection:priority` and `connection:parameters` are now registered as explicitly `unsupported`, with limitations naming the BlueZ D-Bus gap (no LE connection-parameter API on `org.bluez.Device1`/`Adapter1`, BlueZ 5.85), the privilege requirement of the alternatives (`CAP_NET_ADMIN` kernel mgmt socket and root-only debugfs — neither a live per-connection update), and the consequence that GATT traffic may run at a multi-hundred-millisecond peer-negotiated interval. `requestPriority()`, `parameters()`, and `parameterEvents()` fail closed with that reason attached (`BleError.limitations` and `platform.safeMessage`) instead of a bare `capability.unsupported` that taught nothing while a slow link looked like a peripheral fault (#149).
|
|
44
|
+
- BlueZ `security.pair()` no longer fires `Device1.Pair` when the operation is aborted or times out while the just-works agent is still registering; it re-checks cancellation after agent registration so a cancelled pairing never proceeds on the daemon (#143).
|
|
45
|
+
- BlueZ `security.pair()` reports `paired` (not `cancelled`) when an abort or deadline lands after `Device1.Pair` has already completed the bond, so a bond that was actually created is never reported as if it never happened (#143).
|
|
46
|
+
- Android `security.pair()` no longer reports a timed-out pairing as `cancelled` when native cancellation is unavailable and it cannot actually stop the in-flight bond; the deadline path now fails closed with `capability.unsupported`, matching the abort path, so a bond that may still complete is never misreported as cancelled (#143).
|
|
47
|
+
|
|
48
|
+
### Additions
|
|
49
|
+
|
|
50
|
+
- `FindOptions` accepts `duplicates` and `delivery`, so the `find()` convenience can be pointed at a peripheral that advertises in dense bursts instead of being abandoned for a hand-driven `scan()`. Both default to the values `find()` has always used (`'coalesced'` and `'latest'`), so nothing changes for existing callers (#148).
|
|
51
|
+
- BlueZ pairing dispatch: `org.bluez.Device1.Pair`, `org.bluez.Device1.CancelPairing`, and `org.bluez.Adapter1.RemoveDevice` are now allowed through the dbus-next boundary, and the backend registers a just-works (`NoInputNoOutput`) `org.bluez.Agent1` on its own bus (not the system default) so a client-initiated pairing can complete without an external agent (#141, #143).
|
|
52
|
+
|
|
53
|
+
### Changes
|
|
54
|
+
|
|
55
|
+
- The Node convenience factories (`createBluezBleManager` and siblings) now select the first adapter (ordered deterministically by id) when the caller names no `adapterId`, instead of failing on a multi-adapter host. A single-adapter machine needs no configuration and a multi-adapter host picks the same controller every run; pass `adapterId` to target a specific controller (e.g. a second USB dongle used for debugging). The low-level provider is unchanged: it still requires an explicit selection and never silently substitutes one (#143).
|
|
56
|
+
- `PairOptions.secureConnections` (`'require' | 'prefer' | 'disallow'`, default `'prefer'`): request an LE pairing generation. `'prefer'` defers to the platform. No current backend exposes per-pairing generation selection, so `'require'` and `'disallow'` fail closed with `capability.unsupported` on BlueZ, WinRT, Android **and the deterministic `/testing` backend** rather than being silently ignored; the contract is in place for a backend that can honour it. The deterministic backend previously ignored the field and reported a Secure Connections bond under the exact option that forbids one, so a consumer's suite could pass against a contract every real radio rejects — the one way test infrastructure can actively mislead. A test that asserted a successful pair under `'require'` or `'disallow'` now sees `capability.unsupported`, which is what production would have done (#144, #143).
|
|
57
|
+
|
|
58
|
+
### Documentation
|
|
59
|
+
|
|
60
|
+
- Every hardcoded timing and capacity constant in `src/` is now classified as a protocol invariant, a safety bound, or host policy, and each one that stays fixed carries a comment at its definition saying why. Undocumented constants were the defect: a fixed deadline that is never explained reads as a device fault when a slower host misses it (#148).
|
|
61
|
+
- `find()`'s 10 s fallback deadline and `adapter.waitUntilReady()`'s 10 s fallback deadline are named constants shared between the in-process and IPC managers, so the same logical operation cannot expire at two different times either side of the IPC boundary (#148).
|
|
6
62
|
|
|
7
63
|
## [4.0.5] - 2026-08-25
|
|
8
64
|
|
package/MIGRATION_4.0.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Migrating from react-native-ble-plx
|
|
4
4
|
|
|
5
|
-
The current package is `4.0.
|
|
5
|
+
The current package is `4.0.7`. The `4.0.0` release is a new package and a new contract. It is **not a source-compatible rename**. There is no `new BleManager()` facade, no Base64 characteristic values, and no public transaction IDs.
|
|
6
6
|
|
|
7
7
|
This page is for a React Native app that already uses `react-native-ble-plx`. Web, Electron, Node, and Tauri are new hosts — use those pages after you understand the RN rewrite.
|
|
8
8
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
It is an evolution of `react-native-ble-plx`, rewritten as a **cross-platform unified product**. One bytes-first BLE model and lifecycle semantics across hosts, with host-specific construction and ownership. The root package never picks a radio for you, and it will not quietly fall back to a simulator or a different backend.
|
|
8
8
|
|
|
9
|
-
Install `unified-ble-manager` from npm (`4.0.
|
|
9
|
+
Install `unified-ble-manager` from npm (`4.0.7` on `latest`). The root import does not pick a radio. Package SemVer and backend support labels are independent: each radio backend stays Experimental until live-radio evidence says otherwise. See [`docs/PLATFORMS.md`](docs/PLATFORMS.md).
|
|
10
10
|
|
|
11
11
|
> Sponsored by [Imagi Explain](https://imagiexplain.com) — researched, narrated whiteboard explainers from a prompt, a PDF, or your notes.
|
|
12
12
|
|
package/RELEASE.md
CHANGED
|
@@ -81,7 +81,7 @@ Active `4.0.0-rc.*` release-train candidates publish to npm `latest` so a bare `
|
|
|
81
81
|
|
|
82
82
|
On release day, set `release_candidate` to the exact candidate required by the
|
|
83
83
|
release plan. RC2, RC3, RC4, `4.0.0-rc.4.1`, and RC5 are already immutable
|
|
84
|
-
once tagged. Stable `4.0.0`, `4.0.1`, `4.0.2`, and `4.0.
|
|
84
|
+
once tagged. Stable `4.0.0`, `4.0.1`, `4.0.2`, `4.0.3`, `4.0.4`, `4.0.5`, and `4.0.6` are immutable. `4.0.7` is the current train head.
|
|
85
85
|
|
|
86
86
|
```sh
|
|
87
87
|
release_candidate=4.0.0-rc.N
|
|
@@ -133,7 +133,38 @@ The `v4.0.3` tag is immutable published history. Do not recreate or move it.
|
|
|
133
133
|
git tag -a v4.0.3 -m "v4.0.3"
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
## Releasing 4.0.
|
|
136
|
+
## Releasing 4.0.7
|
|
137
|
+
|
|
138
|
+
Same shape as 4.0.6. The release workflow verifies that the tag points at the
|
|
139
|
+
exact current `main` commit before publication; do not create it from a side
|
|
140
|
+
branch or an older commit. Do not retag any immutable version.
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
git fetch origin --tags
|
|
144
|
+
git checkout main
|
|
145
|
+
git pull --ff-only origin main
|
|
146
|
+
|
|
147
|
+
test "$(git branch --show-current)" = "main"
|
|
148
|
+
test "$(node -p "require('./package.json').version")" = "4.0.7"
|
|
149
|
+
git diff --exit-code
|
|
150
|
+
git diff --cached --exit-code
|
|
151
|
+
|
|
152
|
+
git tag -a v4.0.7 -m "v4.0.7"
|
|
153
|
+
git push origin v4.0.7
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Do not push another commit to `main` between the final verification and the tag
|
|
157
|
+
push.
|
|
158
|
+
|
|
159
|
+
Before tagging, confirm the release-notes extraction finds the entry — the
|
|
160
|
+
workflow's awk matches `^## \[4.0.7\]`, and a heading left as `[Unreleased]`
|
|
161
|
+
publishes a stub instead of the changelog:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
awk -v ver=4.0.7 '$0 ~ ("^## \\[" ver "\\]") {p=1;next} p && $0 ~ /^## \[/ {exit} p {print}' CHANGELOG.md
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Releasing 4.0.6
|
|
137
168
|
|
|
138
169
|
The source version is prepared on `main` before the tag. The release workflow verifies that every initial release tag points at the exact current `main` commit before publication; do not create that tag from a side branch or an older commit. Do not retag immutable `v4.0.0`, `v4.0.1`, `v4.0.2`, or `v4.0.3`.
|
|
139
170
|
|
|
@@ -145,12 +176,12 @@ git checkout main
|
|
|
145
176
|
git pull --ff-only origin main
|
|
146
177
|
|
|
147
178
|
test "$(git branch --show-current)" = "main"
|
|
148
|
-
test "$(node -p "require('./package.json').version")" = "4.0.
|
|
179
|
+
test "$(node -p "require('./package.json').version")" = "4.0.6"
|
|
149
180
|
git diff --exit-code
|
|
150
181
|
git diff --cached --exit-code
|
|
151
182
|
|
|
152
|
-
git tag -a v4.0.
|
|
153
|
-
git push origin v4.0.
|
|
183
|
+
git tag -a v4.0.6 -m "v4.0.6"
|
|
184
|
+
git push origin v4.0.6
|
|
154
185
|
```
|
|
155
186
|
|
|
156
187
|
Do not push another commit to `main` between the final verification and the tag push.
|
|
@@ -178,26 +209,34 @@ Stable versions publish to `latest`. Active `4.0.0-rc.*` candidates also publish
|
|
|
178
209
|
|
|
179
210
|
## Post-release verification
|
|
180
211
|
|
|
181
|
-
After the workflow succeeds:
|
|
212
|
+
After the workflow succeeds, verify the registry rather than the workflow log:
|
|
213
|
+
a green publish job and a package a consumer can actually install are not the
|
|
214
|
+
same claim.
|
|
182
215
|
|
|
183
216
|
```sh
|
|
184
|
-
|
|
217
|
+
version=4.0.6
|
|
185
218
|
|
|
186
|
-
npm view "unified-ble-manager@$
|
|
219
|
+
npm view "unified-ble-manager@$version" version
|
|
187
220
|
npm view unified-ble-manager dist-tags --json
|
|
188
|
-
npm view "unified-ble-manager@$
|
|
189
|
-
npm view "unified-ble-manager@$
|
|
221
|
+
npm view "unified-ble-manager@$version" repository --json
|
|
222
|
+
npm view "unified-ble-manager@$version" license
|
|
223
|
+
npm view "unified-ble-manager@$version" dist.integrity
|
|
190
224
|
```
|
|
191
225
|
|
|
192
226
|
Then verify:
|
|
193
227
|
|
|
194
|
-
- npm `latest` resolves to the
|
|
228
|
+
- npm `latest` resolves to the released version (a stable release moves
|
|
229
|
+
`latest`; a prerelease must leave it alone and publish to `next`);
|
|
195
230
|
- the npm package page shows provenance for the published artifact;
|
|
196
|
-
- the GitHub Release exists at
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
|
|
200
|
-
|
|
231
|
+
- the GitHub Release exists at that tag, and is marked prerelease only if the
|
|
232
|
+
version is one;
|
|
233
|
+
- its attached tarball/SBOM/license artifacts correspond to the release
|
|
234
|
+
workflow output;
|
|
235
|
+
- a clean consumer, in a directory outside this repository, can install
|
|
236
|
+
`unified-ble-manager` with no version pin and import the documented host
|
|
237
|
+
entrypoints. This is the check that catches a packaging gap the repository's
|
|
238
|
+
own tests cannot see: `@babel/runtime` shipped undeclared in 4.0.4 and only a
|
|
239
|
+
real external consumer surfaced it.
|
|
201
240
|
|
|
202
241
|
## Failed release or partial publish
|
|
203
242
|
|
package/SBOM.cdx.json
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
"metadata": {
|
|
7
7
|
"component": {
|
|
8
8
|
"type": "library",
|
|
9
|
-
"bom-ref": "pkg:npm/unified-ble-manager@4.0.
|
|
9
|
+
"bom-ref": "pkg:npm/unified-ble-manager@4.0.7",
|
|
10
10
|
"name": "unified-ble-manager",
|
|
11
|
-
"version": "4.0.
|
|
11
|
+
"version": "4.0.7",
|
|
12
12
|
"licenses": [
|
|
13
13
|
{
|
|
14
14
|
"expression": "Apache-2.0"
|
|
15
15
|
}
|
|
16
16
|
],
|
|
17
|
-
"purl": "pkg:npm/unified-ble-manager@4.0.
|
|
17
|
+
"purl": "pkg:npm/unified-ble-manager@4.0.7"
|
|
18
18
|
},
|
|
19
19
|
"properties": [
|
|
20
20
|
{
|
|
@@ -560,7 +560,7 @@
|
|
|
560
560
|
"dependsOn": []
|
|
561
561
|
},
|
|
562
562
|
{
|
|
563
|
-
"ref": "pkg:npm/unified-ble-manager@4.0.
|
|
563
|
+
"ref": "pkg:npm/unified-ble-manager@4.0.7",
|
|
564
564
|
"dependsOn": [
|
|
565
565
|
"pkg:npm/%40babel/runtime@7.29.7",
|
|
566
566
|
"pkg:npm/%40tauri-apps/api@2.11.1",
|
|
@@ -247,13 +247,29 @@ public final class UnifiedBleExpoRuntimeModule extends NativeUnifiedBleExpoRunti
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Read a manifest meta-data flag without assuming how the platform typed it.
|
|
252
|
+
*
|
|
253
|
+
* <p>The config plugin writes {@code android:value="true"}, but the Android manifest parser
|
|
254
|
+
* coerces the literals {@code "true"} and {@code "false"} into a {@link Boolean} before they
|
|
255
|
+
* reach the {@link Bundle}. Reading such an entry with {@code getString} logs a type warning and
|
|
256
|
+
* yields {@code null}, so the caller silently falls back to its default and every configuration
|
|
257
|
+
* digests identically - which defeats the purpose of a digest that exists to detect drift.
|
|
258
|
+
*/
|
|
259
|
+
private static boolean metadataFlag(Bundle metadata, String key, boolean fallback) {
|
|
260
|
+
final Object value = metadata.get(key);
|
|
261
|
+
if (value instanceof Boolean) return (Boolean) value;
|
|
262
|
+
if (value instanceof String) return Boolean.parseBoolean((String) value);
|
|
263
|
+
return fallback;
|
|
264
|
+
}
|
|
265
|
+
|
|
250
266
|
private String configurationDigest(String legacyLocationPolicy) {
|
|
251
267
|
final Bundle metadata = applicationMetadata();
|
|
252
268
|
final String canonical = "unified-ble-expo-runtime-v1\n"
|
|
253
269
|
+ "platform=android\n"
|
|
254
270
|
+ "legacyLocationPolicy=" + legacyLocationPolicy + "\n"
|
|
255
|
-
+ "neverForLocation=" + metadata
|
|
256
|
-
+ "requiredHardware=" + metadata
|
|
271
|
+
+ "neverForLocation=" + metadataFlag(metadata, NEVER_FOR_LOCATION_METADATA, false) + "\n"
|
|
272
|
+
+ "requiredHardware=" + metadataFlag(metadata, REQUIRED_HARDWARE_METADATA, false) + "\n";
|
|
257
273
|
try {
|
|
258
274
|
final byte[] digest = MessageDigest.getInstance("SHA-256")
|
|
259
275
|
.digest(canonical.getBytes(StandardCharsets.UTF_8));
|
|
@@ -6,6 +6,7 @@ import android.bluetooth.BluetoothGattCharacteristic
|
|
|
6
6
|
import android.content.Context
|
|
7
7
|
import android.os.Build
|
|
8
8
|
import android.os.SystemClock
|
|
9
|
+
import com.sfourdrinier.unifiedblemanager.protocol.generated.NATIVE_PROTOCOL_VERSION
|
|
9
10
|
import com.sfourdrinier.unifiedblemanager.protocol.generated.RecordKind
|
|
10
11
|
import com.sfourdrinier.unifiedblemanager.radio.OwnedAndroidGattRadio
|
|
11
12
|
import com.sfourdrinier.unifiedblemanager.radio.OwnedRadioTeardownFailure
|
|
@@ -766,7 +767,7 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
766
767
|
private fun emitSuccess(command: ProtocolWireRecord, kind: String, additions: Map<Int, ProtocolWireValue> = emptyMap()) {
|
|
767
768
|
if (!isPending(command)) return
|
|
768
769
|
val fields = mutableMapOf<Int, ProtocolWireValue>(
|
|
769
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
770
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
770
771
|
2 to ProtocolWireValue.StringValue(kind),
|
|
771
772
|
3 to ProtocolWireValue.RecordValue(terminal(command, "succeeded"))
|
|
772
773
|
)
|
|
@@ -798,7 +799,7 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
798
799
|
val result = ProtocolWireRecord(
|
|
799
800
|
RecordKind.RESULT,
|
|
800
801
|
mapOf(
|
|
801
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
802
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
802
803
|
2 to ProtocolWireValue.StringValue(dispatcherResultKindFor(command.requiredString(3))),
|
|
803
804
|
3 to ProtocolWireValue.RecordValue(terminal(command, "failed", code)),
|
|
804
805
|
10 to ProtocolWireValue.RecordValue(error)
|
|
@@ -814,7 +815,7 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
814
815
|
val result = ProtocolWireRecord(
|
|
815
816
|
RecordKind.RESULT,
|
|
816
817
|
mapOf(
|
|
817
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
818
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
818
819
|
2 to ProtocolWireValue.StringValue("cancelled"),
|
|
819
820
|
3 to ProtocolWireValue.RecordValue(terminal(command, "failed", "cancelled")),
|
|
820
821
|
10 to ProtocolWireValue.RecordValue(
|
|
@@ -841,7 +842,7 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
841
842
|
val result = ProtocolWireRecord(
|
|
842
843
|
RecordKind.RESULT,
|
|
843
844
|
mapOf(
|
|
844
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
845
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
845
846
|
2 to ProtocolWireValue.StringValue("cancelled"),
|
|
846
847
|
3 to ProtocolWireValue.RecordValue(terminal(command, "succeeded")),
|
|
847
848
|
8 to ProtocolWireValue.StringValue(state)
|
|
@@ -866,7 +867,7 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
866
867
|
val event = ProtocolWireRecord(
|
|
867
868
|
RecordKind.EVENT,
|
|
868
869
|
mapOf(
|
|
869
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
870
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
870
871
|
2 to ProtocolWireValue.StringValue("native-security-state-${SystemClock.elapsedRealtimeNanos()}"),
|
|
871
872
|
3 to ProtocolWireValue.StringValue("securityStateChanged"),
|
|
872
873
|
4 to ProtocolWireValue.RecordValue(attachment),
|
|
@@ -1112,7 +1113,7 @@ internal fun connectionLostEvent(
|
|
|
1112
1113
|
return ProtocolWireRecord(
|
|
1113
1114
|
RecordKind.EVENT,
|
|
1114
1115
|
mapOf(
|
|
1115
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
1116
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
1116
1117
|
2 to ProtocolWireValue.StringValue("native-connection-lost-$nativeHandle-$ingressOrdinal"),
|
|
1117
1118
|
3 to ProtocolWireValue.StringValue("connectionLost"),
|
|
1118
1119
|
4 to ProtocolWireValue.RecordValue(connection.requiredRecord(1)),
|
|
@@ -1134,7 +1135,7 @@ internal fun databaseChangedEvent(
|
|
|
1134
1135
|
return ProtocolWireRecord(
|
|
1135
1136
|
RecordKind.EVENT,
|
|
1136
1137
|
mapOf(
|
|
1137
|
-
1 to ProtocolWireValue.UnsignedIntegerValue(
|
|
1138
|
+
1 to ProtocolWireValue.UnsignedIntegerValue(NATIVE_PROTOCOL_VERSION.toLong()),
|
|
1138
1139
|
2 to ProtocolWireValue.StringValue("native-database-changed-$nativeHandle-$ingressOrdinal"),
|
|
1139
1140
|
3 to ProtocolWireValue.StringValue("databaseChanged"),
|
|
1140
1141
|
4 to ProtocolWireValue.RecordValue(attachment),
|
|
@@ -67,6 +67,10 @@ struct JsiEventSinkState final {
|
|
|
67
67
|
protocol::AndroidIngressOrdinalAllocator ingressOrdinalAllocator{1U};
|
|
68
68
|
protocol::AndroidJsiBinaryCleanupLedger binaryCleanupLedger{
|
|
69
69
|
kMaximumBinaryCleanupReferences};
|
|
70
|
+
// A record the codec refuses never reaches JavaScript, so nothing downstream
|
|
71
|
+
// can count it. The tally lives here so the diagnostic that replaces the
|
|
72
|
+
// dropped record can say how many have been lost on this attachment.
|
|
73
|
+
std::atomic<std::uint64_t> quarantinedRecords{0U};
|
|
70
74
|
};
|
|
71
75
|
|
|
72
76
|
std::mutex eventSinkStatesMutex;
|
|
@@ -1339,6 +1343,30 @@ bool deliverNativeResult(
|
|
|
1339
1343
|
const std::shared_ptr<protocol::NativeProtocolControlRuntime>& activeRuntime,
|
|
1340
1344
|
const protocol::ProtocolRecord& result);
|
|
1341
1345
|
|
|
1346
|
+
bool deliverNativeEvent(
|
|
1347
|
+
const std::shared_ptr<JsiEventSinkState>& state,
|
|
1348
|
+
const std::shared_ptr<protocol::NativeProtocolControlRuntime>& activeRuntime,
|
|
1349
|
+
const protocol::ProtocolRecord& event);
|
|
1350
|
+
|
|
1351
|
+
protocol::ProtocolRecord diagnosticEvent(
|
|
1352
|
+
jlong nativeHandle,
|
|
1353
|
+
const std::shared_ptr<JsiEventSinkState>& state,
|
|
1354
|
+
const std::shared_ptr<protocol::NativeProtocolControlRuntime>& activeRuntime,
|
|
1355
|
+
const std::string& code,
|
|
1356
|
+
const std::string& message);
|
|
1357
|
+
|
|
1358
|
+
// Report a record the codec refused. The dropped record cannot be recovered --
|
|
1359
|
+
// it is malformed by definition -- but the application is entitled to learn
|
|
1360
|
+
// that the boundary discarded something it was asked to deliver, which a
|
|
1361
|
+
// logcat line alone does not tell it. The diagnostic is built natively, so it
|
|
1362
|
+
// carries a correct version stamp and survives the validation that the
|
|
1363
|
+
// quarantined record failed.
|
|
1364
|
+
void reportQuarantinedRecord(
|
|
1365
|
+
jlong nativeHandle,
|
|
1366
|
+
const std::shared_ptr<JsiEventSinkState>& state,
|
|
1367
|
+
const std::shared_ptr<protocol::NativeProtocolControlRuntime>& activeRuntime,
|
|
1368
|
+
const char* reason);
|
|
1369
|
+
|
|
1342
1370
|
bool emitRecordFromJava(JNIEnv* environment, jlong nativeHandle, jbyteArray encodedRecord) {
|
|
1343
1371
|
if (encodedRecord == nullptr) {
|
|
1344
1372
|
return false;
|
|
@@ -1403,6 +1431,7 @@ bool emitRecordFromJava(JNIEnv* environment, jlong nativeHandle, jbyteArray enco
|
|
|
1403
1431
|
"UnifiedBleProtocol",
|
|
1404
1432
|
"emitRecordNative quarantined invalid Android record: %s",
|
|
1405
1433
|
error.what());
|
|
1434
|
+
reportQuarantinedRecord(nativeHandle, state, activeRuntime, error.what());
|
|
1406
1435
|
if (decodedRecord.has_value() && decodedRecord->kind == protocol::RecordKind::result) {
|
|
1407
1436
|
requestFatalAttachment(
|
|
1408
1437
|
state,
|
|
@@ -1412,6 +1441,43 @@ bool emitRecordFromJava(JNIEnv* environment, jlong nativeHandle, jbyteArray enco
|
|
|
1412
1441
|
}
|
|
1413
1442
|
}
|
|
1414
1443
|
|
|
1444
|
+
void reportQuarantinedRecord(
|
|
1445
|
+
jlong nativeHandle,
|
|
1446
|
+
const std::shared_ptr<JsiEventSinkState>& state,
|
|
1447
|
+
const std::shared_ptr<protocol::NativeProtocolControlRuntime>& activeRuntime,
|
|
1448
|
+
const char* reason) {
|
|
1449
|
+
const auto quarantined = state->quarantinedRecords.fetch_add(1U) + 1U;
|
|
1450
|
+
try {
|
|
1451
|
+
if (!deliverNativeEvent(
|
|
1452
|
+
state,
|
|
1453
|
+
activeRuntime,
|
|
1454
|
+
diagnosticEvent(
|
|
1455
|
+
nativeHandle,
|
|
1456
|
+
state,
|
|
1457
|
+
activeRuntime,
|
|
1458
|
+
"recordQuarantined",
|
|
1459
|
+
std::string("Android record was quarantined and not delivered (") +
|
|
1460
|
+
std::to_string(quarantined) + " on this attachment): " +
|
|
1461
|
+
(reason == nullptr ? "unknown error" : reason)))) {
|
|
1462
|
+
__android_log_print(
|
|
1463
|
+
ANDROID_LOG_ERROR,
|
|
1464
|
+
"UnifiedBleProtocol",
|
|
1465
|
+
"Android quarantine diagnostic was rejected handle=%lld quarantined=%llu",
|
|
1466
|
+
static_cast<long long>(nativeHandle),
|
|
1467
|
+
static_cast<unsigned long long>(quarantined));
|
|
1468
|
+
}
|
|
1469
|
+
} catch (const std::exception& error) {
|
|
1470
|
+
// The quarantine path is already the failure path; a diagnostic that cannot
|
|
1471
|
+
// be built must not replace the original record's failure with its own.
|
|
1472
|
+
__android_log_print(
|
|
1473
|
+
ANDROID_LOG_ERROR,
|
|
1474
|
+
"UnifiedBleProtocol",
|
|
1475
|
+
"Android quarantine diagnostic could not be built handle=%lld: %s",
|
|
1476
|
+
static_cast<long long>(nativeHandle),
|
|
1477
|
+
error.what());
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1415
1481
|
bool deliverNativeResult(
|
|
1416
1482
|
const std::shared_ptr<JsiEventSinkState>& state,
|
|
1417
1483
|
const std::shared_ptr<protocol::NativeProtocolControlRuntime>& activeRuntime,
|
|
@@ -231,6 +231,23 @@ class UnifiedBleProtocolAndroidFindingsTest {
|
|
|
231
231
|
assertTrue(Regex("return device.getAddress\\(\\)").containsMatchIn(addressProjection))
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
// Regression guard for issue #140: the dispatcher stamped a literal 1 on
|
|
235
|
+
// field 1 of every control-plane record while the codec required 2, so every
|
|
236
|
+
// result it emitted -- including the one describing a failed connect -- was
|
|
237
|
+
// quarantined by the native codec and never reached JavaScript. The stamp has
|
|
238
|
+
// to follow the generated schema, not a number typed next to the field id.
|
|
239
|
+
@Test
|
|
240
|
+
fun `dispatcher records stamp the generated protocol version`() {
|
|
241
|
+
val source = readAndroidSource(
|
|
242
|
+
"android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
assertTrue(source.contains("import com.sfourdrinier.unifiedblemanager.protocol.generated.NATIVE_PROTOCOL_VERSION"))
|
|
246
|
+
val stamps = Regex("""\n\s*1 to ProtocolWireValue\.UnsignedIntegerValue\((.+)\),""").findAll(source).toList()
|
|
247
|
+
assertTrue(stamps.isNotEmpty())
|
|
248
|
+
assertTrue(stamps.all { it.groupValues[1] == "NATIVE_PROTOCOL_VERSION.toLong()" })
|
|
249
|
+
}
|
|
250
|
+
|
|
234
251
|
private fun readAndroidSource(relativePath: String): String {
|
|
235
252
|
val candidates = listOf(
|
|
236
253
|
File(relativePath),
|
|
@@ -23,10 +23,22 @@ normative behavioral authority.
|
|
|
23
23
|
## Contract shape and version skew
|
|
24
24
|
|
|
25
25
|
`BackendProvider` reports loadability, lists adapters, requires a stable
|
|
26
|
-
host-scoped adapter selection
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
host-scoped adapter selection, and creates one backend bound to that adapter.
|
|
27
|
+
Loadability, adapter presence, adapter power, and authorization are different
|
|
28
|
+
states. The low-level provider never silently substitutes a different adapter:
|
|
29
|
+
a named selection that is absent or stale fails `adapter.unavailable`.
|
|
30
|
+
|
|
31
|
+
Choosing a *default* adapter when the caller names none is an ergonomics
|
|
32
|
+
concern that belongs one layer up, not in the provider. The Node convenience
|
|
33
|
+
factories (`createBluezBleManager` and its siblings) select the first adapter,
|
|
34
|
+
ordered deterministically by id, so the common single-adapter machine needs no
|
|
35
|
+
configuration and a multi-adapter host still picks the same controller every
|
|
36
|
+
run; a caller targets a specific controller by passing `adapterId`. This
|
|
37
|
+
supersedes the earlier stance that ambiguous selection must fail: forcing every
|
|
38
|
+
caller to name an adapter is poor ergonomics for the common case. The
|
|
39
|
+
`adapter.selection-required` / `adapter.ambiguous` codes remain in the taxonomy
|
|
40
|
+
for a provider that genuinely cannot pick, but the first-party host factories do
|
|
41
|
+
not push that choice onto the caller.
|
|
30
42
|
|
|
31
43
|
`BleCentralBackend` owns these required components:
|
|
32
44
|
|