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.
Files changed (406) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/MIGRATION_4.0.md +1 -1
  3. package/README.md +1 -1
  4. package/RELEASE.md +55 -16
  5. package/SBOM.cdx.json +4 -4
  6. package/THIRD_PARTY_LICENSES.json +1 -1
  7. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/expo/UnifiedBleExpoRuntimeModule.java +18 -2
  8. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt +8 -7
  9. package/android/src/main/jni/UnifiedBleProtocolJsiBinding.cpp +66 -0
  10. package/android/src/test/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidFindingsTest.kt +17 -0
  11. package/docs/ADR/2026-07-4.0-backend-contract.md +16 -4
  12. package/docs/BONDING.md +144 -7
  13. package/docs/ELECTRON.md +1 -1
  14. package/docs/EXPO_PLUGIN.md +2 -2
  15. package/docs/GAPS.4.0.md +1 -1
  16. package/docs/GETTING_STARTED.md +4 -4
  17. package/docs/NODE.md +2 -2
  18. package/docs/PLATFORMS.md +1 -1
  19. package/docs/UNIFIED_SEMANTICS.md +40 -5
  20. package/docs/WEB.md +1 -1
  21. package/docs/generated/PLATFORM_SUPPORT.md +1 -1
  22. package/lib/commonjs/backend-contract/capabilities.js +11 -0
  23. package/lib/commonjs/backend-contract/capabilities.js.map +1 -1
  24. package/lib/commonjs/backend-contract/ipc.js +7 -0
  25. package/lib/commonjs/backend-contract/ipc.js.map +1 -1
  26. package/lib/commonjs/backend-contract/scan-planning.js +10 -0
  27. package/lib/commonjs/backend-contract/scan-planning.js.map +1 -1
  28. package/lib/commonjs/backend-contract/security.js +54 -0
  29. package/lib/commonjs/backend-contract/security.js.map +1 -1
  30. package/lib/commonjs/backends/bluez/bluez-backend-provider.js +3 -2
  31. package/lib/commonjs/backends/bluez/bluez-backend-provider.js.map +1 -1
  32. package/lib/commonjs/backends/bluez/bluez-backend-runtime.js +1 -0
  33. package/lib/commonjs/backends/bluez/bluez-backend-runtime.js.map +1 -1
  34. package/lib/commonjs/backends/bluez/bluez-backend.js +32 -20
  35. package/lib/commonjs/backends/bluez/bluez-backend.js.map +1 -1
  36. package/lib/commonjs/backends/bluez/bluez-connection-capabilities.js +110 -0
  37. package/lib/commonjs/backends/bluez/bluez-connection-capabilities.js.map +1 -0
  38. package/lib/commonjs/backends/bluez/bluez-dbus-next-boundary.js +179 -0
  39. package/lib/commonjs/backends/bluez/bluez-dbus-next-boundary.js.map +1 -1
  40. package/lib/commonjs/backends/bluez/bluez-operation-dispatcher.js +30 -20
  41. package/lib/commonjs/backends/bluez/bluez-operation-dispatcher.js.map +1 -1
  42. package/lib/commonjs/backends/bluez/bluez-pairing-generation-capability.js +92 -0
  43. package/lib/commonjs/backends/bluez/bluez-pairing-generation-capability.js.map +1 -0
  44. package/lib/commonjs/backends/bluez/bluez-pairing-generation.js +193 -0
  45. package/lib/commonjs/backends/bluez/bluez-pairing-generation.js.map +1 -0
  46. package/lib/commonjs/backends/bluez/bluez-property-waiters.js +10 -0
  47. package/lib/commonjs/backends/bluez/bluez-property-waiters.js.map +1 -1
  48. package/lib/commonjs/backends/bluez/bluez-security.js +97 -8
  49. package/lib/commonjs/backends/bluez/bluez-security.js.map +1 -1
  50. package/lib/commonjs/backends/corebluetooth/corebluetooth-cleanup.js +8 -0
  51. package/lib/commonjs/backends/corebluetooth/corebluetooth-cleanup.js.map +1 -1
  52. package/lib/commonjs/backends/corebluetooth/corebluetooth-connection-controls.js +12 -0
  53. package/lib/commonjs/backends/corebluetooth/corebluetooth-connection-controls.js.map +1 -1
  54. package/lib/commonjs/backends/reactnative/react-native-android-security.js +36 -4
  55. package/lib/commonjs/backends/reactnative/react-native-android-security.js.map +1 -1
  56. package/lib/commonjs/backends/reactnative/react-native-restoration.js +8 -0
  57. package/lib/commonjs/backends/reactnative/react-native-restoration.js.map +1 -1
  58. package/lib/commonjs/backends/winrt/winrt-backend-helpers.js +9 -0
  59. package/lib/commonjs/backends/winrt/winrt-backend-helpers.js.map +1 -1
  60. package/lib/commonjs/backends/winrt/winrt-security.js +23 -10
  61. package/lib/commonjs/backends/winrt/winrt-security.js.map +1 -1
  62. package/lib/commonjs/cli-json.js +7 -0
  63. package/lib/commonjs/cli-json.js.map +1 -1
  64. package/lib/commonjs/core/core-characteristic-operations.js +13 -0
  65. package/lib/commonjs/core/core-characteristic-operations.js.map +1 -1
  66. package/lib/commonjs/core/core-gatt-handles.js +10 -0
  67. package/lib/commonjs/core/core-gatt-handles.js.map +1 -1
  68. package/lib/commonjs/core/operation-coordinator.js +10 -1
  69. package/lib/commonjs/core/operation-coordinator.js.map +1 -1
  70. package/lib/commonjs/core/unified-ble-core.js +9 -0
  71. package/lib/commonjs/core/unified-ble-core.js.map +1 -1
  72. package/lib/commonjs/diagnostics/trace-format.js +12 -0
  73. package/lib/commonjs/diagnostics/trace-format.js.map +1 -1
  74. package/lib/commonjs/electron/connection-event-stream-registry.js +8 -0
  75. package/lib/commonjs/electron/connection-event-stream-registry.js.map +1 -1
  76. package/lib/commonjs/electron/main-binding.js +16 -0
  77. package/lib/commonjs/electron/main-binding.js.map +1 -1
  78. package/lib/commonjs/electron/main-router.js +11 -0
  79. package/lib/commonjs/electron/main-router.js.map +1 -1
  80. package/lib/commonjs/electron/renderer-stream-registry.js +8 -0
  81. package/lib/commonjs/electron/renderer-stream-registry.js.map +1 -1
  82. package/lib/commonjs/electron/renderer.js +11 -0
  83. package/lib/commonjs/electron/renderer.js.map +1 -1
  84. package/lib/commonjs/expo.js +20 -5
  85. package/lib/commonjs/expo.js.map +1 -1
  86. package/lib/commonjs/implementation-version.js +1 -1
  87. package/lib/commonjs/ipc/manager.js +18 -0
  88. package/lib/commonjs/ipc/manager.js.map +1 -1
  89. package/lib/commonjs/ipc/public-manager.js +22 -5
  90. package/lib/commonjs/ipc/public-manager.js.map +1 -1
  91. package/lib/commonjs/native-protocol/rn-android-boundary.js +5 -1
  92. package/lib/commonjs/native-protocol/rn-android-boundary.js.map +1 -1
  93. package/lib/commonjs/node-bluez.js +5 -2
  94. package/lib/commonjs/node-bluez.js.map +1 -1
  95. package/lib/commonjs/node-corebluetooth.js +11 -0
  96. package/lib/commonjs/node-corebluetooth.js.map +1 -1
  97. package/lib/commonjs/node-host-manager.js +14 -6
  98. package/lib/commonjs/node-host-manager.js.map +1 -1
  99. package/lib/commonjs/public/ble-manager.js +81 -14
  100. package/lib/commonjs/public/ble-manager.js.map +1 -1
  101. package/lib/commonjs/public/host-identity.js.map +1 -1
  102. package/lib/commonjs/public/scan-state-budget.js +11 -0
  103. package/lib/commonjs/public/scan-state-budget.js.map +1 -1
  104. package/lib/commonjs/public/security.js +17 -1
  105. package/lib/commonjs/public/security.js.map +1 -1
  106. package/lib/commonjs/public/stream-capacity.js +13 -2
  107. package/lib/commonjs/public/stream-capacity.js.map +1 -1
  108. package/lib/commonjs/tck/first-party/bluez-tck-registration.js +14 -2
  109. package/lib/commonjs/tck/first-party/bluez-tck-registration.js.map +1 -1
  110. package/lib/commonjs/testing/deterministic/deterministic-security.js +16 -3
  111. package/lib/commonjs/testing/deterministic/deterministic-security.js.map +1 -1
  112. package/lib/module/backend-contract/capabilities.js +11 -0
  113. package/lib/module/backend-contract/capabilities.js.map +1 -1
  114. package/lib/module/backend-contract/ipc.js +7 -0
  115. package/lib/module/backend-contract/ipc.js.map +1 -1
  116. package/lib/module/backend-contract/scan-planning.js +10 -0
  117. package/lib/module/backend-contract/scan-planning.js.map +1 -1
  118. package/lib/module/backend-contract/security.js +53 -1
  119. package/lib/module/backend-contract/security.js.map +1 -1
  120. package/lib/module/backends/bluez/bluez-backend-provider.js +3 -2
  121. package/lib/module/backends/bluez/bluez-backend-provider.js.map +1 -1
  122. package/lib/module/backends/bluez/bluez-backend-runtime.js +1 -0
  123. package/lib/module/backends/bluez/bluez-backend-runtime.js.map +1 -1
  124. package/lib/module/backends/bluez/bluez-backend.js +32 -20
  125. package/lib/module/backends/bluez/bluez-backend.js.map +1 -1
  126. package/lib/module/backends/bluez/bluez-connection-capabilities.js +105 -0
  127. package/lib/module/backends/bluez/bluez-connection-capabilities.js.map +1 -0
  128. package/lib/module/backends/bluez/bluez-dbus-next-boundary.js +179 -0
  129. package/lib/module/backends/bluez/bluez-dbus-next-boundary.js.map +1 -1
  130. package/lib/module/backends/bluez/bluez-operation-dispatcher.js +29 -20
  131. package/lib/module/backends/bluez/bluez-operation-dispatcher.js.map +1 -1
  132. package/lib/module/backends/bluez/bluez-pairing-generation-capability.js +87 -0
  133. package/lib/module/backends/bluez/bluez-pairing-generation-capability.js.map +1 -0
  134. package/lib/module/backends/bluez/bluez-pairing-generation.js +188 -0
  135. package/lib/module/backends/bluez/bluez-pairing-generation.js.map +1 -0
  136. package/lib/module/backends/bluez/bluez-property-waiters.js +10 -0
  137. package/lib/module/backends/bluez/bluez-property-waiters.js.map +1 -1
  138. package/lib/module/backends/bluez/bluez-security.js +97 -8
  139. package/lib/module/backends/bluez/bluez-security.js.map +1 -1
  140. package/lib/module/backends/corebluetooth/corebluetooth-cleanup.js +9 -0
  141. package/lib/module/backends/corebluetooth/corebluetooth-cleanup.js.map +1 -1
  142. package/lib/module/backends/corebluetooth/corebluetooth-connection-controls.js +13 -0
  143. package/lib/module/backends/corebluetooth/corebluetooth-connection-controls.js.map +1 -1
  144. package/lib/module/backends/reactnative/react-native-android-security.js +36 -4
  145. package/lib/module/backends/reactnative/react-native-android-security.js.map +1 -1
  146. package/lib/module/backends/reactnative/react-native-restoration.js +8 -0
  147. package/lib/module/backends/reactnative/react-native-restoration.js.map +1 -1
  148. package/lib/module/backends/winrt/winrt-backend-helpers.js +10 -0
  149. package/lib/module/backends/winrt/winrt-backend-helpers.js.map +1 -1
  150. package/lib/module/backends/winrt/winrt-security.js +23 -10
  151. package/lib/module/backends/winrt/winrt-security.js.map +1 -1
  152. package/lib/module/cli-json.js +7 -0
  153. package/lib/module/cli-json.js.map +1 -1
  154. package/lib/module/core/core-characteristic-operations.js +13 -0
  155. package/lib/module/core/core-characteristic-operations.js.map +1 -1
  156. package/lib/module/core/core-gatt-handles.js +10 -0
  157. package/lib/module/core/core-gatt-handles.js.map +1 -1
  158. package/lib/module/core/operation-coordinator.js +10 -1
  159. package/lib/module/core/operation-coordinator.js.map +1 -1
  160. package/lib/module/core/unified-ble-core.js +9 -0
  161. package/lib/module/core/unified-ble-core.js.map +1 -1
  162. package/lib/module/diagnostics/trace-format.js +12 -0
  163. package/lib/module/diagnostics/trace-format.js.map +1 -1
  164. package/lib/module/electron/connection-event-stream-registry.js +8 -0
  165. package/lib/module/electron/connection-event-stream-registry.js.map +1 -1
  166. package/lib/module/electron/main-binding.js +16 -0
  167. package/lib/module/electron/main-binding.js.map +1 -1
  168. package/lib/module/electron/main-router.js +11 -0
  169. package/lib/module/electron/main-router.js.map +1 -1
  170. package/lib/module/electron/renderer-stream-registry.js +8 -0
  171. package/lib/module/electron/renderer-stream-registry.js.map +1 -1
  172. package/lib/module/electron/renderer.js +11 -0
  173. package/lib/module/electron/renderer.js.map +1 -1
  174. package/lib/module/expo.js +20 -5
  175. package/lib/module/expo.js.map +1 -1
  176. package/lib/module/implementation-version.js +1 -1
  177. package/lib/module/ipc/manager.js +18 -0
  178. package/lib/module/ipc/manager.js.map +1 -1
  179. package/lib/module/ipc/public-manager.js +23 -6
  180. package/lib/module/ipc/public-manager.js.map +1 -1
  181. package/lib/module/native-protocol/rn-android-boundary.js +5 -1
  182. package/lib/module/native-protocol/rn-android-boundary.js.map +1 -1
  183. package/lib/module/node-bluez.js +5 -2
  184. package/lib/module/node-bluez.js.map +1 -1
  185. package/lib/module/node-corebluetooth.js +11 -0
  186. package/lib/module/node-corebluetooth.js.map +1 -1
  187. package/lib/module/node-host-manager.js +14 -6
  188. package/lib/module/node-host-manager.js.map +1 -1
  189. package/lib/module/public/ble-manager.js +80 -13
  190. package/lib/module/public/ble-manager.js.map +1 -1
  191. package/lib/module/public/host-identity.js.map +1 -1
  192. package/lib/module/public/scan-state-budget.js +11 -0
  193. package/lib/module/public/scan-state-budget.js.map +1 -1
  194. package/lib/module/public/security.js +17 -1
  195. package/lib/module/public/security.js.map +1 -1
  196. package/lib/module/public/stream-capacity.js +13 -2
  197. package/lib/module/public/stream-capacity.js.map +1 -1
  198. package/lib/module/tck/first-party/bluez-tck-registration.js +14 -2
  199. package/lib/module/tck/first-party/bluez-tck-registration.js.map +1 -1
  200. package/lib/module/testing/deterministic/deterministic-security.js +16 -3
  201. package/lib/module/testing/deterministic/deterministic-security.js.map +1 -1
  202. package/lib/typescript/commonjs/src/backend-contract/capabilities.d.ts +11 -0
  203. package/lib/typescript/commonjs/src/backend-contract/capabilities.d.ts.map +1 -1
  204. package/lib/typescript/commonjs/src/backend-contract/ipc.d.ts +7 -0
  205. package/lib/typescript/commonjs/src/backend-contract/ipc.d.ts.map +1 -1
  206. package/lib/typescript/commonjs/src/backend-contract/scan-planning.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/src/backend-contract/security.d.ts +45 -1
  208. package/lib/typescript/commonjs/src/backend-contract/security.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts +13 -1
  210. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts.map +1 -1
  211. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-runtime.d.ts +8 -0
  212. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-runtime.d.ts.map +1 -1
  213. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend.d.ts +5 -1
  214. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend.d.ts.map +1 -1
  215. package/lib/typescript/commonjs/src/backends/bluez/bluez-connection-capabilities.d.ts +13 -0
  216. package/lib/typescript/commonjs/src/backends/bluez/bluez-connection-capabilities.d.ts.map +1 -0
  217. package/lib/typescript/commonjs/src/backends/bluez/bluez-dbus-contract.d.ts +2 -0
  218. package/lib/typescript/commonjs/src/backends/bluez/bluez-dbus-contract.d.ts.map +1 -1
  219. package/lib/typescript/commonjs/src/backends/bluez/bluez-dbus-next-boundary.d.ts.map +1 -1
  220. package/lib/typescript/commonjs/src/backends/bluez/bluez-operation-dispatcher.d.ts +12 -0
  221. package/lib/typescript/commonjs/src/backends/bluez/bluez-operation-dispatcher.d.ts.map +1 -1
  222. package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation-capability.d.ts +16 -0
  223. package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation-capability.d.ts.map +1 -0
  224. package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation.d.ts +41 -0
  225. package/lib/typescript/commonjs/src/backends/bluez/bluez-pairing-generation.d.ts.map +1 -0
  226. package/lib/typescript/commonjs/src/backends/bluez/bluez-property-waiters.d.ts +10 -0
  227. package/lib/typescript/commonjs/src/backends/bluez/bluez-property-waiters.d.ts.map +1 -1
  228. package/lib/typescript/commonjs/src/backends/bluez/bluez-security.d.ts +0 -1
  229. package/lib/typescript/commonjs/src/backends/bluez/bluez-security.d.ts.map +1 -1
  230. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-cleanup.d.ts +8 -0
  231. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-cleanup.d.ts.map +1 -1
  232. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-connection-controls.d.ts.map +1 -1
  233. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
  234. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
  235. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-security.d.ts +5 -0
  236. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-security.d.ts.map +1 -1
  237. package/lib/typescript/commonjs/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
  238. package/lib/typescript/commonjs/src/backends/reactnative/react-native-restoration.d.ts.map +1 -1
  239. package/lib/typescript/commonjs/src/backends/winrt/winrt-backend-helpers.d.ts +9 -0
  240. package/lib/typescript/commonjs/src/backends/winrt/winrt-backend-helpers.d.ts.map +1 -1
  241. package/lib/typescript/commonjs/src/backends/winrt/winrt-provider.d.ts +1 -1
  242. package/lib/typescript/commonjs/src/backends/winrt/winrt-security.d.ts.map +1 -1
  243. package/lib/typescript/commonjs/src/cli-json.d.ts.map +1 -1
  244. package/lib/typescript/commonjs/src/core/core-characteristic-operations.d.ts.map +1 -1
  245. package/lib/typescript/commonjs/src/core/core-gatt-handles.d.ts.map +1 -1
  246. package/lib/typescript/commonjs/src/core/operation-coordinator.d.ts.map +1 -1
  247. package/lib/typescript/commonjs/src/core/unified-ble-core.d.ts.map +1 -1
  248. package/lib/typescript/commonjs/src/diagnostics/trace-format.d.ts +12 -0
  249. package/lib/typescript/commonjs/src/diagnostics/trace-format.d.ts.map +1 -1
  250. package/lib/typescript/commonjs/src/electron/connection-event-stream-registry.d.ts.map +1 -1
  251. package/lib/typescript/commonjs/src/electron/main-binding.d.ts.map +1 -1
  252. package/lib/typescript/commonjs/src/electron/main-router.d.ts.map +1 -1
  253. package/lib/typescript/commonjs/src/electron/renderer-stream-registry.d.ts.map +1 -1
  254. package/lib/typescript/commonjs/src/electron/renderer.d.ts.map +1 -1
  255. package/lib/typescript/commonjs/src/expo.d.ts.map +1 -1
  256. package/lib/typescript/commonjs/src/implementation-version.d.ts +1 -1
  257. package/lib/typescript/commonjs/src/ipc/manager.d.ts.map +1 -1
  258. package/lib/typescript/commonjs/src/ipc/public-manager.d.ts.map +1 -1
  259. package/lib/typescript/commonjs/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
  260. package/lib/typescript/commonjs/src/node-bluez.d.ts +14 -0
  261. package/lib/typescript/commonjs/src/node-bluez.d.ts.map +1 -1
  262. package/lib/typescript/commonjs/src/node-corebluetooth.d.ts.map +1 -1
  263. package/lib/typescript/commonjs/src/public/ble-manager.d.ts +38 -1
  264. package/lib/typescript/commonjs/src/public/ble-manager.d.ts.map +1 -1
  265. package/lib/typescript/commonjs/src/public/host-identity.d.ts +7 -1
  266. package/lib/typescript/commonjs/src/public/host-identity.d.ts.map +1 -1
  267. package/lib/typescript/commonjs/src/public/scan-state-budget.d.ts +11 -0
  268. package/lib/typescript/commonjs/src/public/scan-state-budget.d.ts.map +1 -1
  269. package/lib/typescript/commonjs/src/public/security.d.ts +6 -0
  270. package/lib/typescript/commonjs/src/public/security.d.ts.map +1 -1
  271. package/lib/typescript/commonjs/src/public/stream-capacity.d.ts +13 -2
  272. package/lib/typescript/commonjs/src/public/stream-capacity.d.ts.map +1 -1
  273. package/lib/typescript/commonjs/src/tck/first-party/bluez-tck-registration.d.ts +1 -1
  274. package/lib/typescript/commonjs/src/tck/first-party/bluez-tck-registration.d.ts.map +1 -1
  275. package/lib/typescript/commonjs/src/testing/deterministic/deterministic-security.d.ts.map +1 -1
  276. package/lib/typescript/module/src/backend-contract/capabilities.d.ts +11 -0
  277. package/lib/typescript/module/src/backend-contract/capabilities.d.ts.map +1 -1
  278. package/lib/typescript/module/src/backend-contract/ipc.d.ts +7 -0
  279. package/lib/typescript/module/src/backend-contract/ipc.d.ts.map +1 -1
  280. package/lib/typescript/module/src/backend-contract/scan-planning.d.ts.map +1 -1
  281. package/lib/typescript/module/src/backend-contract/security.d.ts +45 -1
  282. package/lib/typescript/module/src/backend-contract/security.d.ts.map +1 -1
  283. package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts +13 -1
  284. package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts.map +1 -1
  285. package/lib/typescript/module/src/backends/bluez/bluez-backend-runtime.d.ts +8 -0
  286. package/lib/typescript/module/src/backends/bluez/bluez-backend-runtime.d.ts.map +1 -1
  287. package/lib/typescript/module/src/backends/bluez/bluez-backend.d.ts +5 -1
  288. package/lib/typescript/module/src/backends/bluez/bluez-backend.d.ts.map +1 -1
  289. package/lib/typescript/module/src/backends/bluez/bluez-connection-capabilities.d.ts +13 -0
  290. package/lib/typescript/module/src/backends/bluez/bluez-connection-capabilities.d.ts.map +1 -0
  291. package/lib/typescript/module/src/backends/bluez/bluez-dbus-contract.d.ts +2 -0
  292. package/lib/typescript/module/src/backends/bluez/bluez-dbus-contract.d.ts.map +1 -1
  293. package/lib/typescript/module/src/backends/bluez/bluez-dbus-next-boundary.d.ts.map +1 -1
  294. package/lib/typescript/module/src/backends/bluez/bluez-operation-dispatcher.d.ts +12 -0
  295. package/lib/typescript/module/src/backends/bluez/bluez-operation-dispatcher.d.ts.map +1 -1
  296. package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation-capability.d.ts +16 -0
  297. package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation-capability.d.ts.map +1 -0
  298. package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation.d.ts +41 -0
  299. package/lib/typescript/module/src/backends/bluez/bluez-pairing-generation.d.ts.map +1 -0
  300. package/lib/typescript/module/src/backends/bluez/bluez-property-waiters.d.ts +10 -0
  301. package/lib/typescript/module/src/backends/bluez/bluez-property-waiters.d.ts.map +1 -1
  302. package/lib/typescript/module/src/backends/bluez/bluez-security.d.ts +0 -1
  303. package/lib/typescript/module/src/backends/bluez/bluez-security.d.ts.map +1 -1
  304. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-cleanup.d.ts +8 -0
  305. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-cleanup.d.ts.map +1 -1
  306. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-connection-controls.d.ts.map +1 -1
  307. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
  308. package/lib/typescript/module/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
  309. package/lib/typescript/module/src/backends/reactnative/react-native-android-security.d.ts +5 -0
  310. package/lib/typescript/module/src/backends/reactnative/react-native-android-security.d.ts.map +1 -1
  311. package/lib/typescript/module/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
  312. package/lib/typescript/module/src/backends/reactnative/react-native-restoration.d.ts.map +1 -1
  313. package/lib/typescript/module/src/backends/winrt/winrt-backend-helpers.d.ts +9 -0
  314. package/lib/typescript/module/src/backends/winrt/winrt-backend-helpers.d.ts.map +1 -1
  315. package/lib/typescript/module/src/backends/winrt/winrt-provider.d.ts +1 -1
  316. package/lib/typescript/module/src/backends/winrt/winrt-security.d.ts.map +1 -1
  317. package/lib/typescript/module/src/cli-json.d.ts.map +1 -1
  318. package/lib/typescript/module/src/core/core-characteristic-operations.d.ts.map +1 -1
  319. package/lib/typescript/module/src/core/core-gatt-handles.d.ts.map +1 -1
  320. package/lib/typescript/module/src/core/operation-coordinator.d.ts.map +1 -1
  321. package/lib/typescript/module/src/core/unified-ble-core.d.ts.map +1 -1
  322. package/lib/typescript/module/src/diagnostics/trace-format.d.ts +12 -0
  323. package/lib/typescript/module/src/diagnostics/trace-format.d.ts.map +1 -1
  324. package/lib/typescript/module/src/electron/connection-event-stream-registry.d.ts.map +1 -1
  325. package/lib/typescript/module/src/electron/main-binding.d.ts.map +1 -1
  326. package/lib/typescript/module/src/electron/main-router.d.ts.map +1 -1
  327. package/lib/typescript/module/src/electron/renderer-stream-registry.d.ts.map +1 -1
  328. package/lib/typescript/module/src/electron/renderer.d.ts.map +1 -1
  329. package/lib/typescript/module/src/expo.d.ts.map +1 -1
  330. package/lib/typescript/module/src/implementation-version.d.ts +1 -1
  331. package/lib/typescript/module/src/ipc/manager.d.ts.map +1 -1
  332. package/lib/typescript/module/src/ipc/public-manager.d.ts.map +1 -1
  333. package/lib/typescript/module/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
  334. package/lib/typescript/module/src/node-bluez.d.ts +14 -0
  335. package/lib/typescript/module/src/node-bluez.d.ts.map +1 -1
  336. package/lib/typescript/module/src/node-corebluetooth.d.ts.map +1 -1
  337. package/lib/typescript/module/src/public/ble-manager.d.ts +38 -1
  338. package/lib/typescript/module/src/public/ble-manager.d.ts.map +1 -1
  339. package/lib/typescript/module/src/public/host-identity.d.ts +7 -1
  340. package/lib/typescript/module/src/public/host-identity.d.ts.map +1 -1
  341. package/lib/typescript/module/src/public/scan-state-budget.d.ts +11 -0
  342. package/lib/typescript/module/src/public/scan-state-budget.d.ts.map +1 -1
  343. package/lib/typescript/module/src/public/security.d.ts +6 -0
  344. package/lib/typescript/module/src/public/security.d.ts.map +1 -1
  345. package/lib/typescript/module/src/public/stream-capacity.d.ts +13 -2
  346. package/lib/typescript/module/src/public/stream-capacity.d.ts.map +1 -1
  347. package/lib/typescript/module/src/tck/first-party/bluez-tck-registration.d.ts +1 -1
  348. package/lib/typescript/module/src/tck/first-party/bluez-tck-registration.d.ts.map +1 -1
  349. package/lib/typescript/module/src/testing/deterministic/deterministic-security.d.ts.map +1 -1
  350. package/native/PREBUILDS.json +5 -5
  351. package/native/electron/corebluetooth/prebuilds/darwin-arm64/unified_ble_corebluetooth.node +0 -0
  352. package/native/electron/corebluetooth/prebuilds/darwin-x64/unified_ble_corebluetooth.node +0 -0
  353. package/native/electron/winrt/prebuilds/win32-arm64/unified_ble_winrt.node +0 -0
  354. package/native/electron/winrt/prebuilds/win32-x64/unified_ble_winrt.node +0 -0
  355. package/native/protocol/src/NativeProtocolV2Codec.cpp +48 -4
  356. package/native/tauri/Cargo.toml +14 -0
  357. package/native/tauri/src/btleplug_dispatcher.rs +593 -26
  358. package/native/tauri/src/capabilities.rs +2 -1
  359. package/package.json +1 -1
  360. package/src/backend-contract/capabilities.ts +11 -0
  361. package/src/backend-contract/ipc.ts +7 -0
  362. package/src/backend-contract/scan-planning.ts +9 -0
  363. package/src/backend-contract/security.ts +58 -1
  364. package/src/backends/bluez/bluez-backend-provider.ts +15 -2
  365. package/src/backends/bluez/bluez-backend-runtime.ts +9 -0
  366. package/src/backends/bluez/bluez-backend.ts +46 -26
  367. package/src/backends/bluez/bluez-connection-capabilities.ts +144 -0
  368. package/src/backends/bluez/bluez-dbus-contract.ts +2 -0
  369. package/src/backends/bluez/bluez-dbus-next-boundary.ts +162 -0
  370. package/src/backends/bluez/bluez-operation-dispatcher.ts +39 -34
  371. package/src/backends/bluez/bluez-pairing-generation-capability.ts +100 -0
  372. package/src/backends/bluez/bluez-pairing-generation.ts +220 -0
  373. package/src/backends/bluez/bluez-property-waiters.ts +10 -0
  374. package/src/backends/bluez/bluez-security.ts +134 -22
  375. package/src/backends/corebluetooth/corebluetooth-cleanup.ts +8 -0
  376. package/src/backends/corebluetooth/corebluetooth-connection-controls.ts +12 -0
  377. package/src/backends/reactnative/react-native-android-security.ts +34 -2
  378. package/src/backends/reactnative/react-native-restoration.ts +8 -0
  379. package/src/backends/winrt/winrt-backend-helpers.ts +9 -0
  380. package/src/backends/winrt/winrt-security.ts +23 -3
  381. package/src/cli-json.ts +7 -0
  382. package/src/core/core-characteristic-operations.ts +13 -0
  383. package/src/core/core-gatt-handles.ts +10 -0
  384. package/src/core/operation-coordinator.ts +10 -1
  385. package/src/core/unified-ble-core.ts +9 -0
  386. package/src/diagnostics/trace-format.ts +12 -0
  387. package/src/electron/connection-event-stream-registry.ts +8 -0
  388. package/src/electron/main-binding.ts +16 -0
  389. package/src/electron/main-router.ts +11 -0
  390. package/src/electron/renderer-stream-registry.ts +8 -0
  391. package/src/electron/renderer.ts +11 -0
  392. package/src/expo.ts +20 -5
  393. package/src/implementation-version.ts +1 -1
  394. package/src/ipc/manager.ts +17 -0
  395. package/src/ipc/public-manager.ts +27 -5
  396. package/src/native-protocol/rn-android-boundary.ts +5 -1
  397. package/src/node-bluez.ts +21 -3
  398. package/src/node-corebluetooth.ts +11 -0
  399. package/src/node-host-manager.ts +14 -6
  400. package/src/public/ble-manager.ts +98 -15
  401. package/src/public/host-identity.ts +7 -1
  402. package/src/public/scan-state-budget.ts +11 -0
  403. package/src/public/security.ts +25 -1
  404. package/src/public/stream-capacity.ts +13 -2
  405. package/src/tck/first-party/bluez-tck-registration.ts +19 -3
  406. 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
- ## [Unreleased]
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 `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.
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.5` 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).
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.3` are immutable. `4.0.5` is the current train head.
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.5
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.5"
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.5 -m "v4.0.5"
153
- git push origin v4.0.5
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
- release_candidate=4.0.0-rc.N
217
+ version=4.0.6
185
218
 
186
- npm view "unified-ble-manager@$release_candidate" version
219
+ npm view "unified-ble-manager@$version" version
187
220
  npm view unified-ble-manager dist-tags --json
188
- npm view "unified-ble-manager@$release_candidate" repository --json
189
- npm view "unified-ble-manager@$release_candidate" license
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 selected release candidate;
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 the selected candidate tag and is marked prerelease;
197
- - its attached tarball/SBOM/license artifacts correspond to the release workflow output;
198
- - a clean consumer can install `unified-ble-manager` (no version pin) and import the documented host entrypoints.
199
-
200
- After the later stable tag, repeat the same checks for `4.0.0`. `latest` then moves to `4.0.0`.
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.5",
9
+ "bom-ref": "pkg:npm/unified-ble-manager@4.0.7",
10
10
  "name": "unified-ble-manager",
11
- "version": "4.0.5",
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.5"
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.5",
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",
@@ -4,7 +4,7 @@
4
4
  "schemaVersion": "1.0.0",
5
5
  "package": {
6
6
  "name": "unified-ble-manager",
7
- "version": "4.0.5"
7
+ "version": "4.0.7"
8
8
  },
9
9
  "source": {
10
10
  "method": "pnpm-lock production graph with installed-manifest license audit",
@@ -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.getString(NEVER_FOR_LOCATION_METADATA, "false") + "\n"
256
- + "requiredHardware=" + metadata.getString(REQUIRED_HARDWARE_METADATA, "false") + "\n";
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(1),
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(1),
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(1),
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(1),
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(1),
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(1),
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(1),
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 where necessary, and creates one backend bound to
27
- that adapter. Loadability, adapter presence, adapter power, and authorization
28
- are different states. Selecting ambiguously fails `adapter.selection-required`
29
- or `adapter.ambiguous`; a provider never chooses a convenient default.
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