unified-ble-manager 4.0.24 → 4.0.26

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 (396) hide show
  1. package/CHANGELOG.md +118 -3
  2. package/MIGRATION_4.0.md +1 -1
  3. package/README.md +2 -2
  4. package/RELEASE.md +20 -3
  5. package/SBOM.cdx.json +4 -4
  6. package/THIRD_PARTY_LICENSES.json +1 -1
  7. package/android/build.gradle +4 -0
  8. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt +27 -12
  9. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/radio/OwnedAndroidGattRadio.kt +286 -86
  10. package/android/src/test/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcherLifecycleTest.kt +98 -19
  11. package/android/src/test/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcherRadioIngressTest.kt +216 -0
  12. package/docs/BONDING.md +32 -32
  13. package/docs/ELECTRON.md +1 -1
  14. package/docs/EXPO_PLUGIN.md +1 -1
  15. package/docs/GAPS.4.0.md +1 -1
  16. package/docs/GETTING_STARTED.md +4 -3
  17. package/docs/NODE.md +1 -1
  18. package/docs/README.md +3 -0
  19. package/docs/TUTORIALS.md +2 -0
  20. package/docs/UNIFIED_SEMANTICS.md +4 -2
  21. package/docs/WEB.md +1 -1
  22. package/docs/generated/PLATFORM_SUPPORT.md +1 -1
  23. package/ios/Owned/OwnedCoreBluetoothProtocolRadio.swift +15 -11
  24. package/ios/Owned/OwnedCoreBluetoothProtocolRadioCancellation.swift +62 -0
  25. package/ios/Owned/OwnedCoreBluetoothProtocolRadioSupport.swift +71 -0
  26. package/lib/commonjs/backend-contract/errors.js +16 -0
  27. package/lib/commonjs/backend-contract/errors.js.map +1 -1
  28. package/lib/commonjs/backend-contract/index.js +7 -0
  29. package/lib/commonjs/backend-contract/index.js.map +1 -1
  30. package/lib/commonjs/backend-contract/security.js +10 -0
  31. package/lib/commonjs/backend-contract/security.js.map +1 -1
  32. package/lib/commonjs/backends/bluez/bluez-backend-runtime.js +2 -0
  33. package/lib/commonjs/backends/bluez/bluez-backend-runtime.js.map +1 -1
  34. package/lib/commonjs/backends/bluez/bluez-connection-runtime.js +304 -23
  35. package/lib/commonjs/backends/bluez/bluez-connection-runtime.js.map +1 -1
  36. package/lib/commonjs/backends/bluez/bluez-property-waiters.js +4 -5
  37. package/lib/commonjs/backends/bluez/bluez-property-waiters.js.map +1 -1
  38. package/lib/commonjs/backends/bluez/bluez-runtime-models.js +29 -0
  39. package/lib/commonjs/backends/bluez/bluez-runtime-models.js.map +1 -1
  40. package/lib/commonjs/backends/bluez/bluez-runtime-types.js.map +1 -1
  41. package/lib/commonjs/backends/bluez/bluez-scan-runtime.js +132 -10
  42. package/lib/commonjs/backends/bluez/bluez-scan-runtime.js.map +1 -1
  43. package/lib/commonjs/backends/bluez/bluez-security.js +10 -7
  44. package/lib/commonjs/backends/bluez/bluez-security.js.map +1 -1
  45. package/lib/commonjs/backends/bluez/bluez-subscription-runtime.js +31 -22
  46. package/lib/commonjs/backends/bluez/bluez-subscription-runtime.js.map +1 -1
  47. package/lib/commonjs/backends/corebluetooth/corebluetooth-gatt-operations.js +12 -5
  48. package/lib/commonjs/backends/corebluetooth/corebluetooth-gatt-operations.js.map +1 -1
  49. package/lib/commonjs/backends/corebluetooth/corebluetooth-read-notify-provenance.js +89 -0
  50. package/lib/commonjs/backends/corebluetooth/corebluetooth-read-notify-provenance.js.map +1 -0
  51. package/lib/commonjs/backends/reactnative/react-native-android-security.js +32 -21
  52. package/lib/commonjs/backends/reactnative/react-native-android-security.js.map +1 -1
  53. package/lib/commonjs/backends/winrt/winrt-backend.js +17 -3
  54. package/lib/commonjs/backends/winrt/winrt-backend.js.map +1 -1
  55. package/lib/commonjs/backends/winrt/winrt-gatt-operations.js +10 -4
  56. package/lib/commonjs/backends/winrt/winrt-gatt-operations.js.map +1 -1
  57. package/lib/commonjs/backends/winrt/winrt-handles.js +2 -1
  58. package/lib/commonjs/backends/winrt/winrt-handles.js.map +1 -1
  59. package/lib/commonjs/backends/winrt/winrt-security.js +43 -24
  60. package/lib/commonjs/backends/winrt/winrt-security.js.map +1 -1
  61. package/lib/commonjs/backends/winrt/winrt-subscription-runtime.js +130 -28
  62. package/lib/commonjs/backends/winrt/winrt-subscription-runtime.js.map +1 -1
  63. package/lib/commonjs/core/bounded-stream.js +20 -10
  64. package/lib/commonjs/core/bounded-stream.js.map +1 -1
  65. package/lib/commonjs/core/core-discovery.js +4 -0
  66. package/lib/commonjs/core/core-discovery.js.map +1 -1
  67. package/lib/commonjs/core/core-lifecycle-observer.js +46 -0
  68. package/lib/commonjs/core/core-lifecycle-observer.js.map +1 -1
  69. package/lib/commonjs/core/owned-bounded-stream.js +4 -4
  70. package/lib/commonjs/core/owned-bounded-stream.js.map +1 -1
  71. package/lib/commonjs/core/unified-ble-core.js +260 -94
  72. package/lib/commonjs/core/unified-ble-core.js.map +1 -1
  73. package/lib/commonjs/electron/connection-event-stream-registry.js +4 -1
  74. package/lib/commonjs/electron/connection-event-stream-registry.js.map +1 -1
  75. package/lib/commonjs/electron/renderer-stream-registry.js +4 -1
  76. package/lib/commonjs/electron/renderer-stream-registry.js.map +1 -1
  77. package/lib/commonjs/implementation-version.js +1 -1
  78. package/lib/commonjs/ipc/manager.js +39 -1
  79. package/lib/commonjs/ipc/manager.js.map +1 -1
  80. package/lib/commonjs/ipc/public-manager.js +23 -10
  81. package/lib/commonjs/ipc/public-manager.js.map +1 -1
  82. package/lib/commonjs/native-protocol/rn-android-boundary.js +10 -0
  83. package/lib/commonjs/native-protocol/rn-android-boundary.js.map +1 -1
  84. package/lib/commonjs/public/ble-manager.js +152 -46
  85. package/lib/commonjs/public/ble-manager.js.map +1 -1
  86. package/lib/commonjs/public/cleanup.js +1 -0
  87. package/lib/commonjs/public/cleanup.js.map +1 -1
  88. package/lib/commonjs/public/scan-state.js +46 -0
  89. package/lib/commonjs/public/scan-state.js.map +1 -1
  90. package/lib/commonjs/public/streams.js +12 -7
  91. package/lib/commonjs/public/streams.js.map +1 -1
  92. package/lib/commonjs/tck/first-party/winrt-tck-registration.js +5 -0
  93. package/lib/commonjs/tck/first-party/winrt-tck-registration.js.map +1 -1
  94. package/lib/commonjs/tck/runner-public-scenarios.js +17 -2
  95. package/lib/commonjs/tck/runner-public-scenarios.js.map +1 -1
  96. package/lib/commonjs/testing/deterministic/deterministic-stream.js +16 -5
  97. package/lib/commonjs/testing/deterministic/deterministic-stream.js.map +1 -1
  98. package/lib/commonjs/web/navigator-web-bluetooth-boundary.js +20 -0
  99. package/lib/commonjs/web/navigator-web-bluetooth-boundary.js.map +1 -1
  100. package/lib/commonjs/web/web-bluetooth-backend.js +135 -6
  101. package/lib/commonjs/web/web-bluetooth-backend.js.map +1 -1
  102. package/lib/commonjs/web.js.map +1 -1
  103. package/lib/module/backend-contract/errors.js +15 -0
  104. package/lib/module/backend-contract/errors.js.map +1 -1
  105. package/lib/module/backend-contract/index.js +1 -0
  106. package/lib/module/backend-contract/index.js.map +1 -1
  107. package/lib/module/backend-contract/security.js +11 -0
  108. package/lib/module/backend-contract/security.js.map +1 -1
  109. package/lib/module/backends/bluez/bluez-backend-runtime.js +3 -1
  110. package/lib/module/backends/bluez/bluez-backend-runtime.js.map +1 -1
  111. package/lib/module/backends/bluez/bluez-connection-runtime.js +305 -25
  112. package/lib/module/backends/bluez/bluez-connection-runtime.js.map +1 -1
  113. package/lib/module/backends/bluez/bluez-property-waiters.js +4 -6
  114. package/lib/module/backends/bluez/bluez-property-waiters.js.map +1 -1
  115. package/lib/module/backends/bluez/bluez-runtime-models.js +27 -0
  116. package/lib/module/backends/bluez/bluez-runtime-models.js.map +1 -1
  117. package/lib/module/backends/bluez/bluez-runtime-types.js.map +1 -1
  118. package/lib/module/backends/bluez/bluez-scan-runtime.js +133 -11
  119. package/lib/module/backends/bluez/bluez-scan-runtime.js.map +1 -1
  120. package/lib/module/backends/bluez/bluez-security.js +10 -7
  121. package/lib/module/backends/bluez/bluez-security.js.map +1 -1
  122. package/lib/module/backends/bluez/bluez-subscription-runtime.js +31 -22
  123. package/lib/module/backends/bluez/bluez-subscription-runtime.js.map +1 -1
  124. package/lib/module/backends/corebluetooth/corebluetooth-gatt-operations.js +12 -5
  125. package/lib/module/backends/corebluetooth/corebluetooth-gatt-operations.js.map +1 -1
  126. package/lib/module/backends/corebluetooth/corebluetooth-read-notify-provenance.js +79 -0
  127. package/lib/module/backends/corebluetooth/corebluetooth-read-notify-provenance.js.map +1 -0
  128. package/lib/module/backends/reactnative/react-native-android-security.js +32 -21
  129. package/lib/module/backends/reactnative/react-native-android-security.js.map +1 -1
  130. package/lib/module/backends/winrt/winrt-backend.js +17 -3
  131. package/lib/module/backends/winrt/winrt-backend.js.map +1 -1
  132. package/lib/module/backends/winrt/winrt-gatt-operations.js +12 -6
  133. package/lib/module/backends/winrt/winrt-gatt-operations.js.map +1 -1
  134. package/lib/module/backends/winrt/winrt-handles.js +2 -1
  135. package/lib/module/backends/winrt/winrt-handles.js.map +1 -1
  136. package/lib/module/backends/winrt/winrt-security.js +43 -24
  137. package/lib/module/backends/winrt/winrt-security.js.map +1 -1
  138. package/lib/module/backends/winrt/winrt-subscription-runtime.js +128 -28
  139. package/lib/module/backends/winrt/winrt-subscription-runtime.js.map +1 -1
  140. package/lib/module/core/bounded-stream.js +20 -10
  141. package/lib/module/core/bounded-stream.js.map +1 -1
  142. package/lib/module/core/core-discovery.js +4 -0
  143. package/lib/module/core/core-discovery.js.map +1 -1
  144. package/lib/module/core/core-lifecycle-observer.js +45 -0
  145. package/lib/module/core/core-lifecycle-observer.js.map +1 -1
  146. package/lib/module/core/owned-bounded-stream.js +4 -4
  147. package/lib/module/core/owned-bounded-stream.js.map +1 -1
  148. package/lib/module/core/unified-ble-core.js +261 -95
  149. package/lib/module/core/unified-ble-core.js.map +1 -1
  150. package/lib/module/electron/connection-event-stream-registry.js +5 -2
  151. package/lib/module/electron/connection-event-stream-registry.js.map +1 -1
  152. package/lib/module/electron/renderer-stream-registry.js +5 -2
  153. package/lib/module/electron/renderer-stream-registry.js.map +1 -1
  154. package/lib/module/implementation-version.js +1 -1
  155. package/lib/module/ipc/manager.js +40 -2
  156. package/lib/module/ipc/manager.js.map +1 -1
  157. package/lib/module/ipc/public-manager.js +24 -11
  158. package/lib/module/ipc/public-manager.js.map +1 -1
  159. package/lib/module/native-protocol/rn-android-boundary.js +10 -0
  160. package/lib/module/native-protocol/rn-android-boundary.js.map +1 -1
  161. package/lib/module/public/ble-manager.js +153 -47
  162. package/lib/module/public/ble-manager.js.map +1 -1
  163. package/lib/module/public/cleanup.js +1 -1
  164. package/lib/module/public/cleanup.js.map +1 -1
  165. package/lib/module/public/scan-state.js +44 -0
  166. package/lib/module/public/scan-state.js.map +1 -1
  167. package/lib/module/public/streams.js +13 -8
  168. package/lib/module/public/streams.js.map +1 -1
  169. package/lib/module/tck/first-party/winrt-tck-registration.js +5 -0
  170. package/lib/module/tck/first-party/winrt-tck-registration.js.map +1 -1
  171. package/lib/module/tck/runner-public-scenarios.js +17 -2
  172. package/lib/module/tck/runner-public-scenarios.js.map +1 -1
  173. package/lib/module/testing/deterministic/deterministic-stream.js +16 -5
  174. package/lib/module/testing/deterministic/deterministic-stream.js.map +1 -1
  175. package/lib/module/web/navigator-web-bluetooth-boundary.js +20 -0
  176. package/lib/module/web/navigator-web-bluetooth-boundary.js.map +1 -1
  177. package/lib/module/web/web-bluetooth-backend.js +135 -6
  178. package/lib/module/web/web-bluetooth-backend.js.map +1 -1
  179. package/lib/module/web.js.map +1 -1
  180. package/lib/typescript/commonjs/src/backend-contract/errors.d.ts +2 -0
  181. package/lib/typescript/commonjs/src/backend-contract/errors.d.ts.map +1 -1
  182. package/lib/typescript/commonjs/src/backend-contract/index.d.ts +1 -0
  183. package/lib/typescript/commonjs/src/backend-contract/index.d.ts.map +1 -1
  184. package/lib/typescript/commonjs/src/backend-contract/security.d.ts.map +1 -1
  185. package/lib/typescript/commonjs/src/backend-contract/streams.d.ts +5 -1
  186. package/lib/typescript/commonjs/src/backend-contract/streams.d.ts.map +1 -1
  187. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
  188. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-runtime.d.ts +2 -1
  189. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-runtime.d.ts.map +1 -1
  190. package/lib/typescript/commonjs/src/backends/bluez/bluez-connection-runtime.d.ts +1 -0
  191. package/lib/typescript/commonjs/src/backends/bluez/bluez-connection-runtime.d.ts.map +1 -1
  192. package/lib/typescript/commonjs/src/backends/bluez/bluez-property-waiters.d.ts +1 -0
  193. package/lib/typescript/commonjs/src/backends/bluez/bluez-property-waiters.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/src/backends/bluez/bluez-runtime-models.d.ts +3 -0
  195. package/lib/typescript/commonjs/src/backends/bluez/bluez-runtime-models.d.ts.map +1 -1
  196. package/lib/typescript/commonjs/src/backends/bluez/bluez-runtime-types.d.ts +22 -1
  197. package/lib/typescript/commonjs/src/backends/bluez/bluez-runtime-types.d.ts.map +1 -1
  198. package/lib/typescript/commonjs/src/backends/bluez/bluez-scan-runtime.d.ts.map +1 -1
  199. package/lib/typescript/commonjs/src/backends/bluez/bluez-security.d.ts +1 -1
  200. package/lib/typescript/commonjs/src/backends/bluez/bluez-security.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/src/backends/bluez/bluez-subscription-runtime.d.ts.map +1 -1
  202. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-gatt-operations.d.ts.map +1 -1
  203. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
  204. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-read-notify-provenance.d.ts +37 -0
  205. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-read-notify-provenance.d.ts.map +1 -0
  206. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
  207. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-security.d.ts +1 -1
  208. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-security.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
  210. package/lib/typescript/commonjs/src/backends/winrt/winrt-backend.d.ts +7 -1
  211. package/lib/typescript/commonjs/src/backends/winrt/winrt-backend.d.ts.map +1 -1
  212. package/lib/typescript/commonjs/src/backends/winrt/winrt-boundary.d.ts +1 -0
  213. package/lib/typescript/commonjs/src/backends/winrt/winrt-boundary.d.ts.map +1 -1
  214. package/lib/typescript/commonjs/src/backends/winrt/winrt-gatt-operations.d.ts +1 -0
  215. package/lib/typescript/commonjs/src/backends/winrt/winrt-gatt-operations.d.ts.map +1 -1
  216. package/lib/typescript/commonjs/src/backends/winrt/winrt-handles.d.ts.map +1 -1
  217. package/lib/typescript/commonjs/src/backends/winrt/winrt-provider.d.ts +1 -1
  218. package/lib/typescript/commonjs/src/backends/winrt/winrt-security.d.ts +1 -1
  219. package/lib/typescript/commonjs/src/backends/winrt/winrt-security.d.ts.map +1 -1
  220. package/lib/typescript/commonjs/src/backends/winrt/winrt-subscription-runtime.d.ts +4 -1
  221. package/lib/typescript/commonjs/src/backends/winrt/winrt-subscription-runtime.d.ts.map +1 -1
  222. package/lib/typescript/commonjs/src/core/bounded-stream.d.ts +5 -3
  223. package/lib/typescript/commonjs/src/core/bounded-stream.d.ts.map +1 -1
  224. package/lib/typescript/commonjs/src/core/core-discovery.d.ts.map +1 -1
  225. package/lib/typescript/commonjs/src/core/core-lifecycle-observer.d.ts +10 -0
  226. package/lib/typescript/commonjs/src/core/core-lifecycle-observer.d.ts.map +1 -1
  227. package/lib/typescript/commonjs/src/core/owned-bounded-stream.d.ts +3 -3
  228. package/lib/typescript/commonjs/src/core/owned-bounded-stream.d.ts.map +1 -1
  229. package/lib/typescript/commonjs/src/core/unified-ble-core.d.ts +6 -0
  230. package/lib/typescript/commonjs/src/core/unified-ble-core.d.ts.map +1 -1
  231. package/lib/typescript/commonjs/src/electron/connection-event-stream-registry.d.ts.map +1 -1
  232. package/lib/typescript/commonjs/src/electron/renderer-stream-registry.d.ts.map +1 -1
  233. package/lib/typescript/commonjs/src/implementation-version.d.ts +1 -1
  234. package/lib/typescript/commonjs/src/ipc/manager.d.ts.map +1 -1
  235. package/lib/typescript/commonjs/src/ipc/public-manager.d.ts.map +1 -1
  236. package/lib/typescript/commonjs/src/manager/consumer-handles.d.ts +1 -0
  237. package/lib/typescript/commonjs/src/manager/consumer-handles.d.ts.map +1 -1
  238. package/lib/typescript/commonjs/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
  239. package/lib/typescript/commonjs/src/public/ble-manager.d.ts +13 -2
  240. package/lib/typescript/commonjs/src/public/ble-manager.d.ts.map +1 -1
  241. package/lib/typescript/commonjs/src/public/cleanup.d.ts +3 -1
  242. package/lib/typescript/commonjs/src/public/cleanup.d.ts.map +1 -1
  243. package/lib/typescript/commonjs/src/public/scan-state.d.ts +15 -0
  244. package/lib/typescript/commonjs/src/public/scan-state.d.ts.map +1 -1
  245. package/lib/typescript/commonjs/src/public/streams.d.ts +1 -0
  246. package/lib/typescript/commonjs/src/public/streams.d.ts.map +1 -1
  247. package/lib/typescript/commonjs/src/tck/first-party/winrt-tck-registration.d.ts +1 -0
  248. package/lib/typescript/commonjs/src/tck/first-party/winrt-tck-registration.d.ts.map +1 -1
  249. package/lib/typescript/commonjs/src/tck/runner-public-scenarios.d.ts.map +1 -1
  250. package/lib/typescript/commonjs/src/testing/deterministic/deterministic-stream.d.ts +4 -2
  251. package/lib/typescript/commonjs/src/testing/deterministic/deterministic-stream.d.ts.map +1 -1
  252. package/lib/typescript/commonjs/src/web/navigator-web-bluetooth-boundary.d.ts +9 -1
  253. package/lib/typescript/commonjs/src/web/navigator-web-bluetooth-boundary.d.ts.map +1 -1
  254. package/lib/typescript/commonjs/src/web/web-bluetooth-backend.d.ts +16 -0
  255. package/lib/typescript/commonjs/src/web/web-bluetooth-backend.d.ts.map +1 -1
  256. package/lib/typescript/commonjs/src/web/web-bluetooth-boundary.d.ts +11 -0
  257. package/lib/typescript/commonjs/src/web/web-bluetooth-boundary.d.ts.map +1 -1
  258. package/lib/typescript/commonjs/src/web.d.ts +1 -1
  259. package/lib/typescript/commonjs/src/web.d.ts.map +1 -1
  260. package/lib/typescript/module/src/backend-contract/errors.d.ts +2 -0
  261. package/lib/typescript/module/src/backend-contract/errors.d.ts.map +1 -1
  262. package/lib/typescript/module/src/backend-contract/index.d.ts +1 -0
  263. package/lib/typescript/module/src/backend-contract/index.d.ts.map +1 -1
  264. package/lib/typescript/module/src/backend-contract/security.d.ts.map +1 -1
  265. package/lib/typescript/module/src/backend-contract/streams.d.ts +5 -1
  266. package/lib/typescript/module/src/backend-contract/streams.d.ts.map +1 -1
  267. package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
  268. package/lib/typescript/module/src/backends/bluez/bluez-backend-runtime.d.ts +2 -1
  269. package/lib/typescript/module/src/backends/bluez/bluez-backend-runtime.d.ts.map +1 -1
  270. package/lib/typescript/module/src/backends/bluez/bluez-connection-runtime.d.ts +1 -0
  271. package/lib/typescript/module/src/backends/bluez/bluez-connection-runtime.d.ts.map +1 -1
  272. package/lib/typescript/module/src/backends/bluez/bluez-property-waiters.d.ts +1 -0
  273. package/lib/typescript/module/src/backends/bluez/bluez-property-waiters.d.ts.map +1 -1
  274. package/lib/typescript/module/src/backends/bluez/bluez-runtime-models.d.ts +3 -0
  275. package/lib/typescript/module/src/backends/bluez/bluez-runtime-models.d.ts.map +1 -1
  276. package/lib/typescript/module/src/backends/bluez/bluez-runtime-types.d.ts +22 -1
  277. package/lib/typescript/module/src/backends/bluez/bluez-runtime-types.d.ts.map +1 -1
  278. package/lib/typescript/module/src/backends/bluez/bluez-scan-runtime.d.ts.map +1 -1
  279. package/lib/typescript/module/src/backends/bluez/bluez-security.d.ts +1 -1
  280. package/lib/typescript/module/src/backends/bluez/bluez-security.d.ts.map +1 -1
  281. package/lib/typescript/module/src/backends/bluez/bluez-subscription-runtime.d.ts.map +1 -1
  282. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-gatt-operations.d.ts.map +1 -1
  283. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
  284. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-read-notify-provenance.d.ts +37 -0
  285. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-read-notify-provenance.d.ts.map +1 -0
  286. package/lib/typescript/module/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
  287. package/lib/typescript/module/src/backends/reactnative/react-native-android-security.d.ts +1 -1
  288. package/lib/typescript/module/src/backends/reactnative/react-native-android-security.d.ts.map +1 -1
  289. package/lib/typescript/module/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
  290. package/lib/typescript/module/src/backends/winrt/winrt-backend.d.ts +7 -1
  291. package/lib/typescript/module/src/backends/winrt/winrt-backend.d.ts.map +1 -1
  292. package/lib/typescript/module/src/backends/winrt/winrt-boundary.d.ts +1 -0
  293. package/lib/typescript/module/src/backends/winrt/winrt-boundary.d.ts.map +1 -1
  294. package/lib/typescript/module/src/backends/winrt/winrt-gatt-operations.d.ts +1 -0
  295. package/lib/typescript/module/src/backends/winrt/winrt-gatt-operations.d.ts.map +1 -1
  296. package/lib/typescript/module/src/backends/winrt/winrt-handles.d.ts.map +1 -1
  297. package/lib/typescript/module/src/backends/winrt/winrt-provider.d.ts +1 -1
  298. package/lib/typescript/module/src/backends/winrt/winrt-security.d.ts +1 -1
  299. package/lib/typescript/module/src/backends/winrt/winrt-security.d.ts.map +1 -1
  300. package/lib/typescript/module/src/backends/winrt/winrt-subscription-runtime.d.ts +4 -1
  301. package/lib/typescript/module/src/backends/winrt/winrt-subscription-runtime.d.ts.map +1 -1
  302. package/lib/typescript/module/src/core/bounded-stream.d.ts +5 -3
  303. package/lib/typescript/module/src/core/bounded-stream.d.ts.map +1 -1
  304. package/lib/typescript/module/src/core/core-discovery.d.ts.map +1 -1
  305. package/lib/typescript/module/src/core/core-lifecycle-observer.d.ts +10 -0
  306. package/lib/typescript/module/src/core/core-lifecycle-observer.d.ts.map +1 -1
  307. package/lib/typescript/module/src/core/owned-bounded-stream.d.ts +3 -3
  308. package/lib/typescript/module/src/core/owned-bounded-stream.d.ts.map +1 -1
  309. package/lib/typescript/module/src/core/unified-ble-core.d.ts +6 -0
  310. package/lib/typescript/module/src/core/unified-ble-core.d.ts.map +1 -1
  311. package/lib/typescript/module/src/electron/connection-event-stream-registry.d.ts.map +1 -1
  312. package/lib/typescript/module/src/electron/renderer-stream-registry.d.ts.map +1 -1
  313. package/lib/typescript/module/src/implementation-version.d.ts +1 -1
  314. package/lib/typescript/module/src/ipc/manager.d.ts.map +1 -1
  315. package/lib/typescript/module/src/ipc/public-manager.d.ts.map +1 -1
  316. package/lib/typescript/module/src/manager/consumer-handles.d.ts +1 -0
  317. package/lib/typescript/module/src/manager/consumer-handles.d.ts.map +1 -1
  318. package/lib/typescript/module/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
  319. package/lib/typescript/module/src/public/ble-manager.d.ts +13 -2
  320. package/lib/typescript/module/src/public/ble-manager.d.ts.map +1 -1
  321. package/lib/typescript/module/src/public/cleanup.d.ts +3 -1
  322. package/lib/typescript/module/src/public/cleanup.d.ts.map +1 -1
  323. package/lib/typescript/module/src/public/scan-state.d.ts +15 -0
  324. package/lib/typescript/module/src/public/scan-state.d.ts.map +1 -1
  325. package/lib/typescript/module/src/public/streams.d.ts +1 -0
  326. package/lib/typescript/module/src/public/streams.d.ts.map +1 -1
  327. package/lib/typescript/module/src/tck/first-party/winrt-tck-registration.d.ts +1 -0
  328. package/lib/typescript/module/src/tck/first-party/winrt-tck-registration.d.ts.map +1 -1
  329. package/lib/typescript/module/src/tck/runner-public-scenarios.d.ts.map +1 -1
  330. package/lib/typescript/module/src/testing/deterministic/deterministic-stream.d.ts +4 -2
  331. package/lib/typescript/module/src/testing/deterministic/deterministic-stream.d.ts.map +1 -1
  332. package/lib/typescript/module/src/web/navigator-web-bluetooth-boundary.d.ts +9 -1
  333. package/lib/typescript/module/src/web/navigator-web-bluetooth-boundary.d.ts.map +1 -1
  334. package/lib/typescript/module/src/web/web-bluetooth-backend.d.ts +16 -0
  335. package/lib/typescript/module/src/web/web-bluetooth-backend.d.ts.map +1 -1
  336. package/lib/typescript/module/src/web/web-bluetooth-boundary.d.ts +11 -0
  337. package/lib/typescript/module/src/web/web-bluetooth-boundary.d.ts.map +1 -1
  338. package/lib/typescript/module/src/web.d.ts +1 -1
  339. package/lib/typescript/module/src/web.d.ts.map +1 -1
  340. package/native/PREBUILDS.json +9 -9
  341. package/native/electron/corebluetooth/binding.gyp +2 -1
  342. package/native/electron/corebluetooth/prebuilds/darwin-arm64/unified_ble_corebluetooth.node +0 -0
  343. package/native/electron/corebluetooth/prebuilds/darwin-x64/unified_ble_corebluetooth.node +0 -0
  344. package/native/electron/corebluetooth/src/addon.mm +170 -24
  345. package/native/electron/winrt/prebuilds/win32-arm64/unified_ble_winrt.node +0 -0
  346. package/native/electron/winrt/prebuilds/win32-x64/unified_ble_winrt.node +0 -0
  347. package/native/electron/winrt/src/addon.cpp +19 -0
  348. package/native/electron/winrt/src/winrt-boundary.inc +28 -3
  349. package/native/protocol/CMakeLists.txt +14 -0
  350. package/native/protocol/include/OwnedCoreBluetoothReadNotifyProvenance.hpp +61 -0
  351. package/native/tauri/src/btleplug_dispatcher.rs +1836 -243
  352. package/package.json +1 -1
  353. package/src/backend-contract/errors.ts +20 -0
  354. package/src/backend-contract/index.ts +1 -0
  355. package/src/backend-contract/security.ts +11 -0
  356. package/src/backend-contract/streams.ts +5 -1
  357. package/src/backends/bluez/bluez-backend-runtime.ts +8 -1
  358. package/src/backends/bluez/bluez-connection-runtime.ts +406 -27
  359. package/src/backends/bluez/bluez-property-waiters.ts +8 -6
  360. package/src/backends/bluez/bluez-runtime-models.ts +24 -0
  361. package/src/backends/bluez/bluez-runtime-types.ts +23 -1
  362. package/src/backends/bluez/bluez-scan-runtime.ts +170 -14
  363. package/src/backends/bluez/bluez-security.ts +10 -7
  364. package/src/backends/bluez/bluez-subscription-runtime.ts +41 -22
  365. package/src/backends/corebluetooth/corebluetooth-gatt-operations.ts +12 -5
  366. package/src/backends/corebluetooth/corebluetooth-read-notify-provenance.ts +133 -0
  367. package/src/backends/reactnative/react-native-android-security.ts +35 -17
  368. package/src/backends/winrt/winrt-backend.ts +27 -3
  369. package/src/backends/winrt/winrt-boundary.ts +1 -0
  370. package/src/backends/winrt/winrt-gatt-operations.ts +19 -5
  371. package/src/backends/winrt/winrt-handles.ts +2 -0
  372. package/src/backends/winrt/winrt-security.ts +55 -26
  373. package/src/backends/winrt/winrt-subscription-runtime.ts +201 -38
  374. package/src/core/bounded-stream.ts +27 -11
  375. package/src/core/core-discovery.ts +4 -0
  376. package/src/core/core-lifecycle-observer.ts +54 -1
  377. package/src/core/owned-bounded-stream.ts +5 -5
  378. package/src/core/unified-ble-core.ts +329 -118
  379. package/src/electron/connection-event-stream-registry.ts +3 -1
  380. package/src/electron/renderer-stream-registry.ts +3 -1
  381. package/src/implementation-version.ts +1 -1
  382. package/src/ipc/manager.ts +49 -2
  383. package/src/ipc/public-manager.ts +19 -7
  384. package/src/manager/consumer-handles.ts +1 -0
  385. package/src/native-protocol/rn-android-boundary.ts +10 -0
  386. package/src/public/ble-manager.ts +172 -58
  387. package/src/public/cleanup.ts +1 -1
  388. package/src/public/scan-state.ts +60 -0
  389. package/src/public/streams.ts +15 -8
  390. package/src/tck/first-party/winrt-tck-registration.ts +6 -0
  391. package/src/tck/runner-public-scenarios.ts +21 -5
  392. package/src/testing/deterministic/deterministic-stream.ts +19 -6
  393. package/src/web/navigator-web-bluetooth-boundary.ts +50 -0
  394. package/src/web/web-bluetooth-backend.ts +167 -6
  395. package/src/web/web-bluetooth-boundary.ts +11 -0
  396. package/src/web.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,121 @@ All notable changes to `unified-ble-manager` are documented here.
6
6
 
7
7
  No changes yet.
8
8
 
9
+ ## [4.0.26] - 2026-09-06
10
+
11
+ ### Fixes
12
+
13
+ - A third-party backend can now follow, and pass, the cancellation contract
14
+ 4.0.7 introduced. `cancelOutcomeForPairResult` was exported from
15
+ `backend-contract/security` but re-exported type-only from the barrel, so it
16
+ never reached `/backend-sdk`; first-party backends imported it by deep
17
+ relative path and nothing failed. The TCK still hard-required
18
+ `outcome === 'cancelled'` from both `pair()` and `cancelPairing()`, so a
19
+ backend that correctly reported `'paired'` when the bond won the race failed
20
+ `security-pairing-cancellation-cleans-up` while conforming. The scenario now
21
+ asserts that the two calls do not contradict each other about one pairing,
22
+ checked through the shared mapper rather than against a fixed word.
23
+ `cancelOutcomeForPairResult` also gains a `default` branch so an out-of-contract
24
+ outcome from a third-party backend is named as `protocol.violation` instead of
25
+ surfacing as `undefined` and a raw TypeError several frames away. The `default`
26
+ routes through a `never` parameter, so a new first-party `SecurityPairResult`
27
+ variant is still a compile error (#167).
28
+
29
+ ## [4.0.25] - 2026-09-05
30
+
31
+ ### Fixes
32
+
33
+ - Tauri: separate live correlation capacity from 30s replay tombstones. Routed
34
+ cleanup still admits after a full replay window. Live exhaustion is retryable
35
+ backpressure, not `protocol.violation`. Replay of an old correlation still
36
+ rejects.
37
+ - Tauri: a native connect that completes after its caller/lease is gone gets an
38
+ orphan cleanup owner before compensating disconnect. Reservation and owner
39
+ release together, and only for the matching generation.
40
+ - Tauri: scan-stream failure keeps the scan owner in a stopping state until
41
+ native stop settles. A failed stop stays retryable and blocks a second scan.
42
+ Event and poll emit failures carry structured native diagnostics on the
43
+ shared `source-failed` terminal without inventing drop counters (#173).
44
+ - Tauri: post-await lease/generation checks on unsubscribe and scan stop.
45
+ Old-generation resources quarantine instead of attaching to a replacement
46
+ caller. Compensating unsubscribe after subscribe success uses the same orphan
47
+ owner instead of discarding the outcome.
48
+ - Tauri: overlapping stop requests for the same scan join one native
49
+ `stop_scan()`. Completions carry an attempt token, so a late stop of scan A
50
+ cannot take scan B’s stopping owner.
51
+ - Core: retain unadopted scan/connect leases when stale-admission `stop`/`release`
52
+ fails, and retry that exact lease on later manager cleanup. Aborted, destroyed,
53
+ or deadline-expired callers settle promptly; native work stays owned until
54
+ adopted or compensated.
55
+ - Core: coalesce concurrent `rediscoverGatt` waiters onto one replacement after
56
+ a shared in-flight discovery settles. A starter abort or timeout does not fail
57
+ a sibling waiter with its own admission.
58
+ - Public scan observation overflow is subscriber-local. Consuming only `events`
59
+ at full speed with `duplicates: 'all'` and `overflowPolicy: 'error'` no longer
60
+ terminates the scan because an unused observation queue filled.
61
+ - Drop-policy public scan overflow (`balanced` / `latest` / drop-*) reports an
62
+ overflow notice and keeps scanning. The radio stays up. `overflowPolicy:
63
+ 'error'` (`lossless-bounded`) still fail-closes the consumed view as
64
+ `failed`/`overflow`; physical `stop()` remains cleanup.
65
+ - In-process and IPC scan sessions leave `active` when the host source ends
66
+ without `stop()`. An already-terminal source never publishes `active`; the
67
+ first state is the projected terminal (`failed` for `source-failed` /
68
+ `connection-lost` / `overflow`, `stopped` for ordinary close). That event is
69
+ ended delivery; `stop()` remains the physical cleanup path. The data pump
70
+ still drains advertisements already accepted by `finishWithReason` before
71
+ the observation terminal. Structured `source-failed` errors survive scan-state
72
+ binding and reach `scan.observations`.
73
+ - BlueZ `StopDiscovery` / `StopNotify` release this client’s session even if
74
+ another D-Bus client keeps `Discovering` / `Notifying` true.
75
+ - BlueZ `StartNotify` confirmation failure after native accept keeps a retryable
76
+ `StopNotify` owner instead of orphaning the notify session. A late enablement
77
+ success does not promote `removing` / `enabling-failed` back to `ready`.
78
+ - BlueZ `SetDiscoveryFilter`, `StartDiscovery`, and `ConnectDevice` honor caller
79
+ deadline/abort without dropping a later native allocation.
80
+ - BlueZ keeps a retryable compensating `Disconnect` when `ConnectDevice`
81
+ succeeds after the caller is gone. A failed first Disconnect is retried on
82
+ destroy instead of being logged and forgotten.
83
+ - BlueZ address-connect fallback checks scan-plan compatibility, waits out a
84
+ stopping scan, and widens an excluding live scan for concurrent address
85
+ waiters, restoring the owner filter only after the last borrower leaves
86
+ instead of waiting under a filter that cannot observe the target.
87
+ - Apple CoreBluetooth rejects an independent GATT read while that characteristic
88
+ is notifying, because `didUpdateValueFor` cannot distinguish a read response
89
+ from a notification. Electron/Node CoreBluetooth uses the same policy. A read
90
+ admitted while idle still completes from its ATT response if CCCD enable
91
+ races it; overlapping reads and subscribe-while-read are rejected; fused
92
+ values that cannot be attributed are dropped. Remaining native reject codes
93
+ (iOS 1031/1011/1032, Electron 413/414/415) map to `gatt.read-failed` or
94
+ `gatt.subscribe-failed`, not `platform.failure`.
95
+ - Android `setCharacteristicNotification(...) == false` is a local registration
96
+ failure, not proven link loss. Generation-matched disconnect and GATT status 19
97
+ remain the physical-loss authority. The 4.0.22 250 ms CCCD/disconnect two-signal
98
+ arbitration is unchanged. Notifications that arrive after the CCCD write and
99
+ before `onDescriptorWrite` SUCCESS are staged and flushed to admitted
100
+ subscribers. A later same-generation CCCD SUCCESS claims the arbiter instead
101
+ of leaving the peer enabled after a failed public subscribe.
102
+ - WinRT stages `ValueChanged` notifications that arrive before CCCD enablement
103
+ completes, then flushes them once and in order only to admitted subscribers.
104
+ Staging continues until every pending waiter is admitted, and a per-subscriber
105
+ overflow does not discard siblings’ pending buffer.
106
+ - WinRT subscription invalidation waits are bounded. If native `startNotify`
107
+ never settles, disconnect still completes within the cleanup budget. A late
108
+ enable issues a compensating `stopNotify` when the native CCCD key is vacant,
109
+ and skips that disable only when a replacement generation already occupies it.
110
+ Native notification map entries are keyed by connection generation.
111
+ - `cancelPairing()` honors abort and deadline on Android, WinRT, and BlueZ.
112
+ Admission runs before native cancellation. A timed-out wait is not reported as
113
+ `'cancelled'`. An abandoned waiter leaves the in-flight pairing owned.
114
+ `pair()` abort/deadline keeps the pairing owned until the native result;
115
+ a later native `paired`/`rejected` is authoritative for both `pair()` and
116
+ `cancelPairing()`.
117
+ - Web adapter `watchState()` follows browser `availabilitychanged` instead of
118
+ staying stale until another API samples availability. When that event is
119
+ missing, watches share one bounded `getAvailability()` poll. A completed
120
+ availability sample is applied unless a newer sample has already been applied,
121
+ so an outstanding later probe cannot make `attach()` throw `adapter.unavailable`
122
+ from a stale unavailable cache.
123
+
9
124
  ## [4.0.24] - 2026-09-05
10
125
 
11
126
  ### Fixes
@@ -254,7 +369,7 @@ hardware reproduced a later callback ordering that required the 4.0.22 fix.
254
369
 
255
370
  ### Changed behaviour
256
371
 
257
- - `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).
372
+ - `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).
258
373
 
259
374
  ### Additions
260
375
 
@@ -262,13 +377,13 @@ hardware reproduced a later callback ordering that required the 4.0.22 fix.
262
377
 
263
378
  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.
264
379
 
265
- 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.
380
+ 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.
266
381
 
267
382
  `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).
268
383
 
269
384
  ### Fixes
270
385
 
271
- - 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).
386
+ - 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).
272
387
 
273
388
  ### Known limitations
274
389
 
package/MIGRATION_4.0.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Migrating from react-native-ble-plx
4
4
 
5
- This source targets `4.0.24`. 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
+ This source targets `4.0.26`. 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
@@ -18,7 +18,7 @@ root import does not pick a radio. Package SemVer and backend support labels are
18
18
  independent: each radio backend keeps its evidence-derived label. See
19
19
  [`docs/PLATFORMS.md`](docs/PLATFORMS.md).
20
20
 
21
- This source tree is versioned `4.0.24`. Install the exact version shown in the npm
21
+ This source tree is versioned `4.0.26`. Install the exact version shown in the npm
22
22
  registry. During release preparation, the version in `package.json` can be ahead
23
23
  of npm until the matching tag-driven workflow publishes it; the registry and
24
24
  GitHub release remain authoritative.
@@ -243,7 +243,7 @@ Web Bluetooth replaces the scan with `ble.choose(...)` from a user gesture. The
243
243
 
244
244
  | Member | Use |
245
245
  | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
246
- | `observations` | Bounded stream: `value`, `overflow`, or `terminal` |
246
+ | `observations` | Bounded stream: `value`, `overflow`, or `terminal`. Drop-policy overflow means ads were missed; the scan stays up. `error` fail-closes. |
247
247
  | `events` | Optional derived current-view events: `observed` and monotonic `lost` (`observation.reportLostAfterMs`); unsupported host façades reject this option |
248
248
  | `plan` | Host-owned native/residual planning diagnostics, or `null` when this host has no planner |
249
249
  | `stop()` | End the scan and return a cleanup receipt. `find` already does this. |
package/RELEASE.md CHANGED
@@ -82,8 +82,9 @@ Active `4.0.0-rc.*` release-train candidates publish to npm `latest` so a bare `
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
84
  once tagged. Stable `4.0.0` through `4.0.20` are immutable. The unpublished
85
- `v4.0.21` tag is also immutable after its cancelled workflow. `4.0.22` and
86
- `4.0.23` are immutable published history. `4.0.24` is the current train head.
85
+ `v4.0.21` tag is also immutable after its cancelled workflow. `4.0.22`,
86
+ `4.0.23`, `4.0.24`, and `4.0.25` are immutable tagged history. This branch
87
+ prepares `4.0.26`.
87
88
 
88
89
  ```sh
89
90
  release_candidate=4.0.0-rc.N
@@ -111,6 +112,22 @@ The first stable tag `v4.0.0` is immutable published history. Do not recreate or
111
112
  git tag -a v4.0.0 -m "v4.0.0"
112
113
  ```
113
114
 
115
+ ## Releasing 4.0.26
116
+
117
+ Release `v4.0.26` only from the exact current `main` commit after its canonical
118
+ CI succeeds. Verify `package.json` is `4.0.26`, the worktree is clean, and
119
+ release-note extraction finds `## [4.0.26]`. Push a new annotated `v4.0.26`
120
+ tag; never move the immutable `v4.0.25` tag. Follow the required local validation,
121
+ publish workflow, and registry verification below.
122
+
123
+ ## Releasing 4.0.25
124
+
125
+ Release `v4.0.25` only from the exact current `main` commit after its canonical
126
+ CI succeeds. Verify `package.json` is `4.0.25`, the worktree is clean, and
127
+ release-note extraction finds `## [4.0.25]`. Push a new annotated `v4.0.25`
128
+ tag; never move the immutable `v4.0.24` tag. Follow the required local validation,
129
+ publish workflow, and registry verification below.
130
+
114
131
  ## Releasing 4.0.24
115
132
 
116
133
  Release `v4.0.24` only from the exact current `main` commit after its canonical
@@ -491,7 +508,7 @@ a green publish job and a package a consumer can actually install are not the
491
508
  same claim.
492
509
 
493
510
  ```sh
494
- version=4.0.24
511
+ version=4.0.26
495
512
 
496
513
  npm view "unified-ble-manager@$version" version
497
514
  npm view unified-ble-manager dist-tags --json
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.24",
9
+ "bom-ref": "pkg:npm/unified-ble-manager@4.0.26",
10
10
  "name": "unified-ble-manager",
11
- "version": "4.0.24",
11
+ "version": "4.0.26",
12
12
  "licenses": [
13
13
  {
14
14
  "expression": "Apache-2.0"
15
15
  }
16
16
  ],
17
- "purl": "pkg:npm/unified-ble-manager@4.0.24"
17
+ "purl": "pkg:npm/unified-ble-manager@4.0.26"
18
18
  },
19
19
  "properties": [
20
20
  {
@@ -537,7 +537,7 @@
537
537
  "dependsOn": []
538
538
  },
539
539
  {
540
- "ref": "pkg:npm/unified-ble-manager@4.0.24",
540
+ "ref": "pkg:npm/unified-ble-manager@4.0.26",
541
541
  "dependsOn": [
542
542
  "pkg:npm/%40babel/runtime@7.29.7",
543
543
  "pkg:npm/node-addon-api@8.9.0",
@@ -4,7 +4,7 @@
4
4
  "schemaVersion": "1.0.0",
5
5
  "package": {
6
6
  "name": "unified-ble-manager",
7
- "version": "4.0.24"
7
+ "version": "4.0.26"
8
8
  },
9
9
  "source": {
10
10
  "method": "pnpm-lock production graph with installed-manifest license audit",
@@ -89,6 +89,9 @@ android {
89
89
  kotlinOptions {
90
90
  jvmTarget = "17"
91
91
  }
92
+ testOptions {
93
+ unitTests.returnDefaultValues = true
94
+ }
92
95
  externalNativeBuild {
93
96
  cmake {
94
97
  path file("src/main/jni/CMakeLists.txt")
@@ -105,6 +108,7 @@ dependencies {
105
108
  implementation "com.facebook.react:react-android"
106
109
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
107
110
  testImplementation "junit:junit:4.13.2"
111
+ testImplementation "org.mockito:mockito-core:5.14.2"
108
112
  }
109
113
 
110
114
  react {
@@ -30,6 +30,7 @@ class UnifiedBleProtocolAndroidDispatcher(
30
30
  private val establishedConnections = ConcurrentHashMap<String, ProtocolWireRecord>()
31
31
  private val activeDatabases = ConcurrentHashMap<String, ProtocolWireRecord>()
32
32
  private val activeSubscriptions = ConcurrentHashMap<String, SubscriptionRoute>()
33
+ private val pendingSubscriptions = ConcurrentHashMap<String, SubscriptionRoute>()
33
34
  private val radioOperationIds = ConcurrentHashMap<String, Long>()
34
35
  private val activeScanCommand = AtomicReference<ProtocolWireRecord?>(null)
35
36
  private val cancelledScanCommands = ConcurrentHashMap<String, ProtocolWireRecord>()
@@ -78,11 +79,7 @@ class UnifiedBleProtocolAndroidDispatcher(
78
79
  activeDatabases.remove(deviceKey)
79
80
  failPendingCommandsForDevice(deviceKey, "Android GATT link was lost")
80
81
  if (established != null) {
81
- activeSubscriptions.entries.forEach { entry ->
82
- if (entry.value.endpoint.deviceId.equals(deviceId, ignoreCase = true)) {
83
- activeSubscriptions.remove(entry.key, entry.value)
84
- }
85
- }
82
+ clearSubscriptionRoutesForDevice(deviceId)
86
83
  emitConnectionLost(established, status)
87
84
  }
88
85
  }
@@ -134,7 +131,7 @@ class UnifiedBleProtocolAndroidDispatcher(
134
131
  }
135
132
  }
136
133
  radio.onProtocolNotification = { deviceId, characteristic, value ->
137
- activeSubscriptions.values
134
+ (pendingSubscriptions.values.asSequence() + activeSubscriptions.values.asSequence())
138
135
  .filter { route -> route.matches(deviceId, characteristic, radio) }
139
136
  .forEach { route ->
140
137
  UnifiedBleProtocolJsiBinding.emitNotification(nativeHandle, route.subscriptionId, value)
@@ -166,6 +163,7 @@ class UnifiedBleProtocolAndroidDispatcher(
166
163
  }
167
164
  establishedConnections.clear()
168
165
  activeDatabases.clear()
166
+ pendingSubscriptions.clear()
169
167
  activeSubscriptions.clear()
170
168
  }
171
169
 
@@ -272,6 +270,7 @@ class UnifiedBleProtocolAndroidDispatcher(
272
270
  pendingConnects.clear()
273
271
  establishedConnections.clear()
274
272
  activeDatabases.clear()
273
+ pendingSubscriptions.clear()
275
274
  activeSubscriptions.clear()
276
275
  activeScanCommand.set(null)
277
276
  }
@@ -601,6 +600,14 @@ class UnifiedBleProtocolAndroidDispatcher(
601
600
  return
602
601
  }
603
602
  val endpoint = characteristicEndpoint(command.requiredRecord(4))
603
+ val route = SubscriptionRoute(
604
+ subscriptionId,
605
+ endpoint,
606
+ command.optionalString(21)
607
+ )
608
+ if (enable) {
609
+ pendingSubscriptions[subscriptionId] = route
610
+ }
604
611
  val radioOperationId = radio.setNotifyExact(
605
612
  endpoint.deviceId,
606
613
  endpoint.serviceUuid,
@@ -613,6 +620,7 @@ class UnifiedBleProtocolAndroidDispatcher(
613
620
  result.fold(
614
621
  onSuccess = {
615
622
  if (!isPending(command)) {
623
+ pendingSubscriptions.remove(subscriptionId, route)
616
624
  if (enable) {
617
625
  radio.setNotifyExact(
618
626
  endpoint.deviceId,
@@ -634,17 +642,16 @@ class UnifiedBleProtocolAndroidDispatcher(
634
642
  return@fold
635
643
  }
636
644
  if (enable) {
637
- activeSubscriptions[subscriptionId] = SubscriptionRoute(
638
- subscriptionId,
639
- endpoint,
640
- command.optionalString(21)
641
- )
645
+ pendingSubscriptions.remove(subscriptionId, route)
646
+ activeSubscriptions[subscriptionId] = route
642
647
  } else {
648
+ pendingSubscriptions.remove(subscriptionId)
643
649
  activeSubscriptions.remove(subscriptionId)
644
650
  }
645
651
  emitSuccess(command, if (enable) "subscribed" else "unsubscribed")
646
652
  },
647
653
  onFailure = { error ->
654
+ pendingSubscriptions.remove(subscriptionId, route)
648
655
  emitGattOperationFailure(command, "subscriptionFailed", error, "Android CCCD operation failed")
649
656
  }
650
657
  )
@@ -665,6 +672,7 @@ class UnifiedBleProtocolAndroidDispatcher(
665
672
  pendingConnects.clear()
666
673
  establishedConnections.clear()
667
674
  activeDatabases.clear()
675
+ pendingSubscriptions.clear()
668
676
  activeSubscriptions.clear()
669
677
  if (result.isSuccessful) {
670
678
  activeScanCommand.set(null)
@@ -737,7 +745,9 @@ class UnifiedBleProtocolAndroidDispatcher(
737
745
  }
738
746
  if (commandKind == "subscribe") {
739
747
  val endpoint = characteristicEndpoint(command.requiredRecord(4))
740
- activeSubscriptions.remove(command.requiredString(7))
748
+ val subscriptionId = command.requiredString(7)
749
+ pendingSubscriptions.remove(subscriptionId)
750
+ activeSubscriptions.remove(subscriptionId)
741
751
  radio.setNotifyExact(
742
752
  endpoint.deviceId,
743
753
  endpoint.serviceUuid,
@@ -1137,6 +1147,11 @@ class UnifiedBleProtocolAndroidDispatcher(
1137
1147
  }
1138
1148
 
1139
1149
  private fun clearSubscriptionRoutesForDevice(deviceId: String) {
1150
+ pendingSubscriptions.entries.forEach { entry ->
1151
+ if (entry.value.endpoint.deviceId.equals(deviceId, ignoreCase = true)) {
1152
+ pendingSubscriptions.remove(entry.key, entry.value)
1153
+ }
1154
+ }
1140
1155
  activeSubscriptions.entries.forEach { entry ->
1141
1156
  if (entry.value.endpoint.deviceId.equals(deviceId, ignoreCase = true)) {
1142
1157
  activeSubscriptions.remove(entry.key, entry.value)