unified-ble-manager 4.0.5 → 4.0.6
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 +33 -1
- package/MIGRATION_4.0.md +1 -1
- package/README.md +1 -1
- package/RELEASE.md +24 -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 +42 -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/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/backends/bluez/bluez-backend-provider.js +1 -1
- package/lib/commonjs/backends/bluez/bluez-backend-provider.js.map +1 -1
- package/lib/commonjs/backends/bluez/bluez-backend.js +2 -1
- 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-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 +69 -2
- 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 +15 -1
- 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 +7 -0
- 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-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 +4 -0
- 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 +8 -0
- package/lib/commonjs/testing/deterministic/deterministic-security.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/backends/bluez/bluez-backend-provider.js +1 -1
- package/lib/module/backends/bluez/bluez-backend-provider.js.map +1 -1
- package/lib/module/backends/bluez/bluez-backend.js +2 -1
- 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-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 +69 -2
- 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 +15 -1
- 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 +7 -0
- 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-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 +4 -0
- 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 +8 -0
- package/lib/module/testing/deterministic/deterministic-security.js.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 +7 -0
- 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 +1 -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-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.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-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/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 +7 -0
- 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 +1 -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-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.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-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 +422 -10
- package/package.json +1 -1
- package/src/backend-contract/ipc.ts +7 -0
- package/src/backend-contract/scan-planning.ts +9 -0
- package/src/backend-contract/security.ts +7 -0
- package/src/backends/bluez/bluez-backend-provider.ts +1 -1
- package/src/backends/bluez/bluez-backend.ts +3 -1
- 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-property-waiters.ts +10 -0
- package/src/backends/bluez/bluez-security.ts +72 -2
- 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 +15 -1
- 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 +9 -0
- 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-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 +15 -0
- 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 +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `unified-ble-manager` are documented here.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [4.0.6] - 2026-08-26
|
|
6
|
+
|
|
7
|
+
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`.
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
- 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).
|
|
12
|
+
|
|
13
|
+
- 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).
|
|
14
|
+
|
|
15
|
+
- `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).
|
|
16
|
+
- 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).
|
|
17
|
+
- 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).
|
|
18
|
+
- `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).
|
|
19
|
+
- 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).
|
|
20
|
+
- 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).
|
|
21
|
+
- 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).
|
|
22
|
+
- 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).
|
|
23
|
+
|
|
24
|
+
### Additions
|
|
25
|
+
|
|
26
|
+
- `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).
|
|
27
|
+
- 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).
|
|
28
|
+
|
|
29
|
+
### Changes
|
|
30
|
+
|
|
31
|
+
- 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).
|
|
32
|
+
- `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).
|
|
33
|
+
|
|
34
|
+
### Documentation
|
|
35
|
+
|
|
36
|
+
- 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).
|
|
37
|
+
- `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
38
|
|
|
7
39
|
## [4.0.5] - 2026-08-25
|
|
8
40
|
|
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.6`. 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.6` 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`, and `4.0.5` are immutable. `4.0.6` is the current train head.
|
|
85
85
|
|
|
86
86
|
```sh
|
|
87
87
|
release_candidate=4.0.0-rc.N
|
|
@@ -133,7 +133,7 @@ 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.6
|
|
137
137
|
|
|
138
138
|
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
139
|
|
|
@@ -145,12 +145,12 @@ git checkout main
|
|
|
145
145
|
git pull --ff-only origin main
|
|
146
146
|
|
|
147
147
|
test "$(git branch --show-current)" = "main"
|
|
148
|
-
test "$(node -p "require('./package.json').version")" = "4.0.
|
|
148
|
+
test "$(node -p "require('./package.json').version")" = "4.0.6"
|
|
149
149
|
git diff --exit-code
|
|
150
150
|
git diff --cached --exit-code
|
|
151
151
|
|
|
152
|
-
git tag -a v4.0.
|
|
153
|
-
git push origin v4.0.
|
|
152
|
+
git tag -a v4.0.6 -m "v4.0.6"
|
|
153
|
+
git push origin v4.0.6
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
Do not push another commit to `main` between the final verification and the tag push.
|
|
@@ -178,26 +178,34 @@ Stable versions publish to `latest`. Active `4.0.0-rc.*` candidates also publish
|
|
|
178
178
|
|
|
179
179
|
## Post-release verification
|
|
180
180
|
|
|
181
|
-
After the workflow succeeds:
|
|
181
|
+
After the workflow succeeds, verify the registry rather than the workflow log:
|
|
182
|
+
a green publish job and a package a consumer can actually install are not the
|
|
183
|
+
same claim.
|
|
182
184
|
|
|
183
185
|
```sh
|
|
184
|
-
|
|
186
|
+
version=4.0.6
|
|
185
187
|
|
|
186
|
-
npm view "unified-ble-manager@$
|
|
188
|
+
npm view "unified-ble-manager@$version" version
|
|
187
189
|
npm view unified-ble-manager dist-tags --json
|
|
188
|
-
npm view "unified-ble-manager@$
|
|
189
|
-
npm view "unified-ble-manager@$
|
|
190
|
+
npm view "unified-ble-manager@$version" repository --json
|
|
191
|
+
npm view "unified-ble-manager@$version" license
|
|
192
|
+
npm view "unified-ble-manager@$version" dist.integrity
|
|
190
193
|
```
|
|
191
194
|
|
|
192
195
|
Then verify:
|
|
193
196
|
|
|
194
|
-
- npm `latest` resolves to the
|
|
197
|
+
- npm `latest` resolves to the released version (a stable release moves
|
|
198
|
+
`latest`; a prerelease must leave it alone and publish to `next`);
|
|
195
199
|
- the npm package page shows provenance for the published artifact;
|
|
196
|
-
- the GitHub Release exists at
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
- the GitHub Release exists at that tag, and is marked prerelease only if the
|
|
201
|
+
version is one;
|
|
202
|
+
- its attached tarball/SBOM/license artifacts correspond to the release
|
|
203
|
+
workflow output;
|
|
204
|
+
- a clean consumer, in a directory outside this repository, can install
|
|
205
|
+
`unified-ble-manager` with no version pin and import the documented host
|
|
206
|
+
entrypoints. This is the check that catches a packaging gap the repository's
|
|
207
|
+
own tests cannot see: `@babel/runtime` shipped undeclared in 4.0.4 and only a
|
|
208
|
+
real external consumer surfaced it.
|
|
201
209
|
|
|
202
210
|
## Failed release or partial publish
|
|
203
211
|
|
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.6",
|
|
10
10
|
"name": "unified-ble-manager",
|
|
11
|
-
"version": "4.0.
|
|
11
|
+
"version": "4.0.6",
|
|
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.6"
|
|
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.6",
|
|
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
|
|
package/docs/BONDING.md
CHANGED
|
@@ -42,14 +42,49 @@ replay an uncertain GATT write. Use the explicit `withRequiredSecurity` helper
|
|
|
42
42
|
when the application wants a state check followed by an explicitly authorized
|
|
43
43
|
pairing attempt and one callback invocation.
|
|
44
44
|
|
|
45
|
-
Windows and BlueZ
|
|
46
|
-
public APIs provide it.
|
|
45
|
+
Windows and BlueZ expose system pairing and durable unpairing where their
|
|
46
|
+
public APIs provide it.
|
|
47
|
+
|
|
48
|
+
On BlueZ, `pair()` calls `org.bluez.Device1.Pair` and `unpair()` calls
|
|
49
|
+
`org.bluez.Adapter1.RemoveDevice`; the library registers a just-works
|
|
50
|
+
(`NoInputNoOutput`) `org.bluez.Agent1` on its own bus so the system-mediated
|
|
51
|
+
ceremony can complete without an external agent. Just-works confirmation is
|
|
52
|
+
auto-accepted; passkey and PIN *entry* ceremonies are rejected, matching the
|
|
53
|
+
`NoInputNoOutput` capability. The agent is registered (not made the system
|
|
54
|
+
default) so it applies to pairings this client initiates. This dispatch and
|
|
55
|
+
agent policy are covered by unit tests, but their behavior against a live BlueZ
|
|
56
|
+
daemon is a separate capability the first-party backend does not yet prove: the
|
|
57
|
+
TCK marks `bluez:pairing-agent` unsupported for exactly that reason (a
|
|
58
|
+
deterministic boundary cannot exercise a real SMP exchange), so treat live
|
|
59
|
+
bonding as unverified until it is measured on real radio.
|
|
60
|
+
|
|
61
|
+
Android reports public bond-state transitions but does
|
|
47
62
|
not ship a reflection-based remove-bond operation. On the current Expo SDK 57 /
|
|
48
|
-
Android API 36 artifact,
|
|
49
|
-
the public `cancelBondProcess` API is newer
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
Android API 36 artifact, native pairing cancellation is also unavailable because
|
|
64
|
+
the public `cancelBondProcess` API is newer. When cancellation is unavailable
|
|
65
|
+
the library cannot stop an in-flight OS ceremony, so rather than claim a cancel
|
|
66
|
+
it did not perform, an aborted or timed-out `pair()` fails closed by rejecting
|
|
67
|
+
with `capability.unsupported`; a bond may still reach a later terminal state,
|
|
68
|
+
which `watch()` reports.
|
|
69
|
+
|
|
70
|
+
This is a deliberate cross-backend difference callers should handle: where a
|
|
71
|
+
backend can cancel an in-flight pairing (BlueZ, WinRT), an aborted or timed-out
|
|
72
|
+
attempt resolves `{ outcome: 'cancelled' }`; where it cannot (an Android build
|
|
73
|
+
without the cancellation extension), the attempt rejects with
|
|
74
|
+
`capability.unsupported` instead of misreporting a cancellation that did not
|
|
75
|
+
happen.
|
|
76
|
+
|
|
77
|
+
There is a second, narrower window: an abort or deadline that lands after the
|
|
78
|
+
bond has *already* completed but before `pair()` has returned. On BlueZ this is
|
|
79
|
+
reported truthfully as `paired` (never `cancelled`), because the native pairing
|
|
80
|
+
call resolves only on a completed bond. On Android and WinRT the public outcome
|
|
81
|
+
is currently committed when the abort fires, before the native result is
|
|
82
|
+
observed, so this race can surface `cancelled` for a peer that did in fact
|
|
83
|
+
bond; the bond is still authoritative and visible through `watch()`/`state()`.
|
|
84
|
+
Aligning Android and WinRT with BlueZ's report-the-bond behavior is tracked
|
|
85
|
+
separately.
|
|
86
|
+
|
|
87
|
+
Apple and Web keep generic
|
|
53
88
|
pairing/bonding unsupported where their public APIs do not provide a truthful
|
|
54
89
|
measurement; Web `forget()` remains origin-authorization revocation, not
|
|
55
90
|
`unpair`.
|
package/docs/ELECTRON.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Main owns the radio. The renderer uses a versioned IPC client and never loads a native addon.
|
|
6
6
|
|
|
7
|
-
The current package is `4.0.
|
|
7
|
+
The current package is `4.0.6`. The release ships Node-API v8 prebuilds covering macOS and Windows `arm64`/`x64` for both Node and modern Electron.
|
|
8
8
|
|
|
9
9
|
`unified-ble-manager/electron/main` and
|
|
10
10
|
`unified-ble-manager/electron/renderer` are the only Electron entrypoints.
|
package/docs/EXPO_PLUGIN.md
CHANGED
|
@@ -7,7 +7,7 @@ start a radio, request runtime permissions during prebuild, or prove physical
|
|
|
7
7
|
radio/restoration reliability. Expo Go is not a supported BLE execution
|
|
8
8
|
environment because it cannot contain this native module.
|
|
9
9
|
|
|
10
|
-
Use the v2 plugin options in the published `4.0.
|
|
10
|
+
Use the v2 plugin options in the published `4.0.6` package. Those options match
|
|
11
11
|
the schema introduced at `4.0.0-rc.4`. Expo Go cannot load this native module.
|
|
12
12
|
|
|
13
13
|
## Installation and development build
|
|
@@ -15,7 +15,7 @@ the schema introduced at `4.0.0-rc.4`. Expo Go cannot load this native module.
|
|
|
15
15
|
Pin the package so a later `latest` bump does not change native plugin options
|
|
16
16
|
without a rebuild:
|
|
17
17
|
|
|
18
|
-
pnpm add unified-ble-manager@4.0.
|
|
18
|
+
pnpm add unified-ble-manager@4.0.6
|
|
19
19
|
pnpm add expo@^57.0.0 expo-dev-client
|
|
20
20
|
npx expo prebuild --clean
|
|
21
21
|
npx expo run:ios
|
package/docs/GAPS.4.0.md
CHANGED
|
@@ -14,7 +14,7 @@ This file tracks platform code, CI, package, lab, and live-radio evidence. It do
|
|
|
14
14
|
|
|
15
15
|
The clean-baseline contract, unified core, public manager, deterministic backend, TCK, native protocol, first-party backend implementations, host-isolated package exports, SDK/CLI, and legacy-absence gates exist in the 4.0 source. Passing deterministic, compile, ABI, or package tests are implementation proof; they do not become physical-radio support evidence unless a retained record proves the corresponding live scenario.
|
|
16
16
|
|
|
17
|
-
`unified-ble-manager@4.0.
|
|
17
|
+
`unified-ble-manager@4.0.6` is the published, immutable stable package/API. Immutable `4.0.0`, `4.0.1`, `4.0.2`, and `4.0.3` remain published history. Backend support labels remain
|
|
18
18
|
evidence-derived. This package does not rewrite the evidence inventory: a backend remains at the support level
|
|
19
19
|
justified by its records. The alpha train, including `v4.0.0-alpha.40`, remains historical implementation/release
|
|
20
20
|
evidence and must not be used to claim a higher current support label than it actually proved.
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
This page gets you to a first scan, connect, read, notify, and teardown on React Native. Other hosts are linked at the bottom. The root import does not turn Bluetooth on.
|
|
6
6
|
|
|
7
|
-
The current package is `4.0.
|
|
7
|
+
The current package is `4.0.6`.
|
|
8
8
|
|
|
9
9
|
## Pick a host
|
|
10
10
|
|
|
@@ -27,7 +27,7 @@ The current package is `4.0.5`.
|
|
|
27
27
|
Pin the current package for a known native rebuild:
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
|
-
pnpm add unified-ble-manager@4.0.
|
|
30
|
+
pnpm add unified-ble-manager@4.0.6
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Declare Android Bluetooth permissions and the BLE hardware feature yourself,
|
|
@@ -37,10 +37,10 @@ request runtime permissions on Android 12+, add
|
|
|
37
37
|
#### Expo / CNG v2
|
|
38
38
|
|
|
39
39
|
The Expo v2 schema and `unified-ble-manager/expo` factory are in the published
|
|
40
|
-
`4.0.
|
|
40
|
+
`4.0.6` package. Pin that exact version:
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
pnpm add unified-ble-manager@4.0.
|
|
43
|
+
pnpm add unified-ble-manager@4.0.6
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
The package does not run in Expo Go.
|
package/docs/NODE.md
CHANGED
|
@@ -10,7 +10,7 @@ The root import does not open an adapter. Pick one backend:
|
|
|
10
10
|
| `unified-ble-manager/node/winrt` | Windows |
|
|
11
11
|
| `unified-ble-manager/node/bluez` | Linux (needs `dbus-next@^0.10.2` in the app) |
|
|
12
12
|
|
|
13
|
-
The current package is `4.0.
|
|
13
|
+
The current package is `4.0.6`. The release ships Node-API v8 prebuilds for macOS and Windows on `arm64` and `x64`. A normal install should not compile native code. BlueZ talks D-Bus and has no addon.
|
|
14
14
|
|
|
15
15
|
## One-call factories
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@ import { createBluezBleManager } from 'unified-ble-manager/node/bluez'
|
|
|
22
22
|
const manager = await createCoreBluetoothBleManager()
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
If there is no adapter, the factory throws `adapter.unavailable`. If more than one adapter exists and you omit `adapterId`,
|
|
25
|
+
If there is no adapter, the factory throws `adapter.unavailable`. If more than one adapter exists and you omit `adapterId`, the factory picks the first adapter in a deterministic order (by adapter id) so the common single-adapter case needs no configuration and a multi-adapter host still selects the same controller every run; pass `adapterId` (e.g. a BlueZ path like `/org/bluez/hci1`) to target a specific controller — an unusual need, for example a second USB Bluetooth dongle used for debugging. Missing native artifacts throw `capability.unavailable` — there is no fallback to Noble, Web Bluetooth, or a simulator. Expected Node engines are those in `package.json`.
|
|
26
26
|
|
|
27
27
|
On `SIGINT`/`SIGTERM`, await `manager.destroy()`. Then scan/connect/GATT with the same `BleManager` helpers as React Native.
|
|
28
28
|
|
package/docs/PLATFORMS.md
CHANGED
|
@@ -8,7 +8,7 @@ This page is an evidence index, not a static compatibility matrix. An applicatio
|
|
|
8
8
|
|
|
9
9
|
## Package stability and backend support are separate
|
|
10
10
|
|
|
11
|
-
`unified-ble-manager@4.0.
|
|
11
|
+
`unified-ble-manager@4.0.6` is the published **stable package/API** for the 4.x contract; it is immutable. Backend support labels remain evidence-derived and independent of this SemVer. Immutable `4.0.0`, `4.0.1`, `4.0.2`, and `4.0.3` remain published history.
|
|
12
12
|
|
|
13
13
|
This package is the portable API/semantics freeze; it does **not** mean every first-party backend is
|
|
14
14
|
automatically Preview, Supported, or Reliability-qualified.
|