unified-ble-manager 4.0.11 → 4.0.13

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 (98) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/MIGRATION_4.0.md +1 -1
  3. package/README.md +13 -1
  4. package/RELEASE.md +37 -8
  5. package/ROADMAP.4.0.md +4 -0
  6. package/SBOM.cdx.json +4 -4
  7. package/THIRD_PARTY_LICENSES.json +1 -1
  8. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt +11 -4
  9. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolControlModule.java +6 -5
  10. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolJsiBinding.java +4 -4
  11. package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/generated/NativeProtocolV2Schema.kt +7 -1
  12. package/android/src/main/jni/UnifiedBleProtocolJsiBinding.cpp +10 -5
  13. package/android/src/test/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcherLifecycleTest.kt +15 -0
  14. package/docs/AGENTS.md +33 -0
  15. package/docs/ELECTRON.md +1 -1
  16. package/docs/EXPO_PLUGIN.md +1 -1
  17. package/docs/GAPS.4.0.md +1 -1
  18. package/docs/GETTING_STARTED.md +3 -3
  19. package/docs/NODE.md +1 -1
  20. package/docs/README.md +178 -0
  21. package/docs/WEB.md +1 -1
  22. package/docs/generated/PLATFORM_SUPPORT.md +1 -1
  23. package/ios/Generated/NativeProtocolV2Schema.swift +7 -1
  24. package/lib/commonjs/backends/corebluetooth/corebluetooth-backend.js +6 -4
  25. package/lib/commonjs/backends/corebluetooth/corebluetooth-backend.js.map +1 -1
  26. package/lib/commonjs/backends/corebluetooth/corebluetooth-gatt-operations.js +5 -2
  27. package/lib/commonjs/backends/corebluetooth/corebluetooth-gatt-operations.js.map +1 -1
  28. package/lib/commonjs/backends/corebluetooth/corebluetooth-handles.js +20 -0
  29. package/lib/commonjs/backends/corebluetooth/corebluetooth-handles.js.map +1 -1
  30. package/lib/commonjs/implementation-version.js +1 -1
  31. package/lib/commonjs/native-protocol/generated/native-protocol-v2-schema.js +5 -3
  32. package/lib/commonjs/native-protocol/generated/native-protocol-v2-schema.js.map +1 -1
  33. package/lib/commonjs/native-protocol/rn-android-boundary.js +32 -16
  34. package/lib/commonjs/native-protocol/rn-android-boundary.js.map +1 -1
  35. package/lib/module/backends/corebluetooth/corebluetooth-backend.js +6 -4
  36. package/lib/module/backends/corebluetooth/corebluetooth-backend.js.map +1 -1
  37. package/lib/module/backends/corebluetooth/corebluetooth-gatt-operations.js +5 -2
  38. package/lib/module/backends/corebluetooth/corebluetooth-gatt-operations.js.map +1 -1
  39. package/lib/module/backends/corebluetooth/corebluetooth-handles.js +20 -0
  40. package/lib/module/backends/corebluetooth/corebluetooth-handles.js.map +1 -1
  41. package/lib/module/implementation-version.js +1 -1
  42. package/lib/module/native-protocol/generated/native-protocol-v2-schema.js +4 -2
  43. package/lib/module/native-protocol/generated/native-protocol-v2-schema.js.map +1 -1
  44. package/lib/module/native-protocol/rn-android-boundary.js +32 -16
  45. package/lib/module/native-protocol/rn-android-boundary.js.map +1 -1
  46. package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
  47. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-backend.d.ts +2 -1
  48. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-backend.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-boundary.d.ts +4 -0
  50. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-boundary.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-gatt-operations.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-handles.d.ts +2 -1
  53. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-handles.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
  55. package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
  56. package/lib/typescript/commonjs/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
  57. package/lib/typescript/commonjs/src/backends/winrt/winrt-provider.d.ts +1 -1
  58. package/lib/typescript/commonjs/src/implementation-version.d.ts +1 -1
  59. package/lib/typescript/commonjs/src/native-protocol/generated/native-protocol-v2-schema.d.ts +3 -1
  60. package/lib/typescript/commonjs/src/native-protocol/generated/native-protocol-v2-schema.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/src/native-protocol/rn-android-boundary.d.ts +2 -1
  62. package/lib/typescript/commonjs/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
  63. package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
  64. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-backend.d.ts +2 -1
  65. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-backend.d.ts.map +1 -1
  66. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-boundary.d.ts +4 -0
  67. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-boundary.d.ts.map +1 -1
  68. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-gatt-operations.d.ts.map +1 -1
  69. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-handles.d.ts +2 -1
  70. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-handles.d.ts.map +1 -1
  71. package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
  72. package/lib/typescript/module/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
  73. package/lib/typescript/module/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
  74. package/lib/typescript/module/src/backends/winrt/winrt-provider.d.ts +1 -1
  75. package/lib/typescript/module/src/implementation-version.d.ts +1 -1
  76. package/lib/typescript/module/src/native-protocol/generated/native-protocol-v2-schema.d.ts +3 -1
  77. package/lib/typescript/module/src/native-protocol/generated/native-protocol-v2-schema.d.ts.map +1 -1
  78. package/lib/typescript/module/src/native-protocol/rn-android-boundary.d.ts +2 -1
  79. package/lib/typescript/module/src/native-protocol/rn-android-boundary.d.ts.map +1 -1
  80. package/native/AGENTS.md +18 -0
  81. package/native/PREBUILDS.json +5 -5
  82. package/native/electron/corebluetooth/index.js +12 -0
  83. package/native/electron/corebluetooth/prebuilds/darwin-arm64/unified_ble_corebluetooth.node +0 -0
  84. package/native/electron/corebluetooth/prebuilds/darwin-x64/unified_ble_corebluetooth.node +0 -0
  85. package/native/electron/winrt/prebuilds/win32-arm64/unified_ble_winrt.node +0 -0
  86. package/native/electron/winrt/prebuilds/win32-x64/unified_ble_winrt.node +0 -0
  87. package/native/protocol/generated/NativeProtocolV2Schema.hpp +11 -3
  88. package/native/protocol/schema/native-protocol-v2-abi.json +4 -2
  89. package/native/protocol/schema/native-protocol-v2.json +4 -2
  90. package/native/protocol/src/NativeProtocolV2Codec.cpp +3 -1
  91. package/package.json +2 -2
  92. package/src/backends/corebluetooth/corebluetooth-backend.ts +9 -5
  93. package/src/backends/corebluetooth/corebluetooth-boundary.ts +9 -0
  94. package/src/backends/corebluetooth/corebluetooth-gatt-operations.ts +8 -2
  95. package/src/backends/corebluetooth/corebluetooth-handles.ts +36 -1
  96. package/src/implementation-version.ts +1 -1
  97. package/src/native-protocol/generated/native-protocol-v2-schema.ts +6 -1
  98. package/src/native-protocol/rn-android-boundary.ts +53 -20
package/CHANGELOG.md CHANGED
@@ -6,6 +6,51 @@ All notable changes to `unified-ble-manager` are documented here.
6
6
 
7
7
  No changes yet.
8
8
 
9
+ ## [4.0.13] - 2026-09-01
10
+
11
+ ### Fixes
12
+
13
+ - Accept the versioned notification-delivery field on native `subscribe`
14
+ commands while continuing to reject it on `unsubscribe`. This fixes Android
15
+ notification setup failing before CCCD dispatch in 4.0.12.
16
+ - Advance the Native Protocol ABI to 7 so JavaScript built for delivery-mode
17
+ commands rejects an older native binary during handshake instead of failing
18
+ later during the first subscription.
19
+
20
+ ## [4.0.12] - 2026-08-31
21
+
22
+ ### Fixes
23
+
24
+ - Use React Native's architecture-neutral JavaScript call-invoker holder for
25
+ the Android JSI protocol transport. Native result and notification delivery
26
+ now work under both bridgeless and legacy React Native runtimes without
27
+ changing the public API or wire protocol.
28
+ - Preserve the requested notification or indication delivery mode through the
29
+ React Native Android backend and native dispatcher instead of allowing the
30
+ native layer to silently choose a different CCCD mode.
31
+ - Correct React Native protocol ownership and teardown races: binary write
32
+ payloads are released by exactly one owner, connection loss rejects pending
33
+ operations with the normalized lifecycle error, and an in-flight disconnect
34
+ remains pending until the authoritative native teardown terminal arrives.
35
+ - Treat expected, typed direct-GATT link loss as lifecycle state rather than an
36
+ unconditional console error across shared CoreBluetooth-backed hosts. The
37
+ lifecycle event and recovery contract are unchanged.
38
+
39
+ ### Documentation
40
+
41
+ - Add `docs/README.md`, a complete documentation map labelling every document
42
+ Current, Historical, or Generated, and add `llms.txt`, a machine-readable
43
+ package overview for AI coding agents covering the contract facts and every
44
+ public entrypoint. Both are drift-gated: `pnpm docs:check` now fails when a
45
+ document is missing from the map, a listed document does not exist, or
46
+ `llms.txt` stops covering a public entrypoint.
47
+ - Label the remaining lineage documents (pre-4.x changelogs, delivered 4.0
48
+ roadmap, tvOS 3.x spec, v1 README/migration) with explicit historical-record
49
+ banners, and mark `docs/audits/`, `docs/review/`, and `docs/superpowers/` as
50
+ frozen record directories.
51
+ - Add scoped agent guidance (`docs/AGENTS.md`, `scripts/AGENTS.md`,
52
+ `native/AGENTS.md`) stating the conventions specific to those trees.
53
+
9
54
  ## [4.0.11] - 2026-08-30
10
55
 
11
56
  ### Documentation
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.11`. 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.13`. 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
@@ -2,6 +2,12 @@
2
2
 
3
3
  # Unified BLE Manager
4
4
 
5
+ > **AI agent?** Writing code *against* this package: read [`llms.txt`](llms.txt)
6
+ > first — contract facts, every public entrypoint, curated doc links, one fetch.
7
+ > Working *on* this repository: read [`AGENTS.md`](AGENTS.md), then the
8
+ > [documentation map](docs/README.md). Do not infer 4.x behavior from
9
+ > `react-native-ble-plx` 3.x docs or training data.
10
+
5
11
  `unified-ble-manager` is a Bluetooth Low Energy **central** library. You pick a host — React Native, Web, Electron, Tauri, or Node — create one manager, talk to a peripheral in bytes, cancel work with `AbortSignal`, and destroy what you create.
6
12
 
7
13
  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.
@@ -12,7 +18,7 @@ root import does not pick a radio. Package SemVer and backend support labels are
12
18
  independent: each radio backend keeps its evidence-derived label. See
13
19
  [`docs/PLATFORMS.md`](docs/PLATFORMS.md).
14
20
 
15
- This source tree is versioned `4.0.11`. Install the exact version shown in the npm
21
+ This source tree is versioned `4.0.13`. Install the exact version shown in the npm
16
22
  registry. During release preparation, the version in `package.json` can be ahead
17
23
  of npm until the matching tag-driven workflow publishes it; the registry and
18
24
  GitHub release remain authoritative.
@@ -37,6 +43,12 @@ GitHub release remain authoritative.
37
43
  | [`docs/WEB.md`](docs/WEB.md) · [`docs/ELECTRON.md`](docs/ELECTRON.md) · [`docs/NODE.md`](docs/NODE.md) · [`docs/TAURI.md`](docs/TAURI.md) · [`docs/EXPO_PLUGIN.md`](docs/EXPO_PLUGIN.md) | Host construction |
38
44
  | [`docs/PEERS.md`](docs/PEERS.md) | Scoped peer directories, persistence, and reconnect-by-reference |
39
45
  | [`docs/PROFILES_AND_COMMANDS.md`](docs/PROFILES_AND_COMMANDS.md) | Heart Rate, Battery, DIS, and path helpers |
46
+ | [`docs/README.md`](docs/README.md) | Every document in the repository, with live/historical/generated status |
47
+
48
+ Writing code with an AI agent? [`llms.txt`](llms.txt) is the machine-readable
49
+ package overview — contract facts, every public entrypoint, and curated doc
50
+ links in one fetch. Agents contributing to this repository start at
51
+ [`AGENTS.md`](AGENTS.md) and the [documentation map](docs/README.md).
40
52
 
41
53
  ## Install
42
54
 
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` through `4.0.10` are immutable. `4.0.11` is the current train head.
84
+ once tagged. Stable `4.0.0` through `4.0.12` are immutable. `4.0.13` is the current train head.
85
85
 
86
86
  ```sh
87
87
  release_candidate=4.0.0-rc.N
@@ -133,9 +133,9 @@ 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.11
136
+ ## Releasing 4.0.13
137
137
 
138
- The `v4.0.11` tag must identify the exact current `main` commit after canonical
138
+ The `v4.0.13` tag must identify the exact current `main` commit after canonical
139
139
  CI passes. Do not tag this feature branch directly.
140
140
 
141
141
  ```sh
@@ -144,15 +144,44 @@ git checkout main
144
144
  git pull --ff-only origin main
145
145
 
146
146
  test "$(git branch --show-current)" = "main"
147
- test "$(node -p "require('./package.json').version")" = "4.0.11"
147
+ test "$(node -p "require('./package.json').version")" = "4.0.13"
148
148
  git diff --exit-code
149
149
  git diff --cached --exit-code
150
150
 
151
- git tag -a v4.0.11 -m "v4.0.11"
152
- git push origin v4.0.11
151
+ git tag -a v4.0.13 -m "v4.0.13"
152
+ git push origin v4.0.13
153
+ ```
154
+
155
+ Before tagging, confirm release-note extraction finds `## [4.0.13]`.
156
+
157
+ ## Releasing 4.0.12
158
+
159
+ The `v4.0.12` tag must identify the exact current `main` commit after canonical
160
+ CI passes. Do not tag this feature branch directly.
161
+
162
+ ```sh
163
+ git fetch origin --tags
164
+ git checkout main
165
+ git pull --ff-only origin main
166
+
167
+ test "$(git branch --show-current)" = "main"
168
+ test "$(node -p "require('./package.json').version")" = "4.0.12"
169
+ git diff --exit-code
170
+ git diff --cached --exit-code
171
+
172
+ git tag -a v4.0.12 -m "v4.0.12"
173
+ git push origin v4.0.12
153
174
  ```
154
175
 
155
- Before tagging, confirm release-note extraction finds `## [4.0.11]`.
176
+ Before tagging, confirm release-note extraction finds `## [4.0.12]`.
177
+
178
+ ## Releasing 4.0.11
179
+
180
+ The `v4.0.11` tag is immutable published history. Do not recreate or move it.
181
+
182
+ ```sh
183
+ git tag -a v4.0.11 -m "v4.0.11"
184
+ ```
156
185
 
157
186
  ## Releasing 4.0.10
158
187
 
@@ -298,7 +327,7 @@ a green publish job and a package a consumer can actually install are not the
298
327
  same claim.
299
328
 
300
329
  ```sh
301
- version=4.0.11
330
+ version=4.0.12
302
331
 
303
332
  npm view "unified-ble-manager@$version" version
304
333
  npm view unified-ble-manager dist-tags --json
package/ROADMAP.4.0.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  # Roadmap 4.0 — `unified-ble-manager`
4
4
 
5
+ > **Status: Historical record.** This roadmap's scope was delivered with the
6
+ > stable 4.0 releases. It is kept as a decision record and is not current
7
+ > guidance. See the [documentation map](docs/README.md).
8
+
5
9
  **Status:** implemented 4.0 product scope; stable package/API release target
6
10
 
7
11
  **Architecture authority:** [`docs/UNIFIED_BLE_4.0_IMPLEMENTATION_PLAN.md`](docs/UNIFIED_BLE_4.0_IMPLEMENTATION_PLAN.md)
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.11",
9
+ "bom-ref": "pkg:npm/unified-ble-manager@4.0.13",
10
10
  "name": "unified-ble-manager",
11
- "version": "4.0.11",
11
+ "version": "4.0.13",
12
12
  "licenses": [
13
13
  {
14
14
  "expression": "Apache-2.0"
15
15
  }
16
16
  ],
17
- "purl": "pkg:npm/unified-ble-manager@4.0.11"
17
+ "purl": "pkg:npm/unified-ble-manager@4.0.13"
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.11",
540
+ "ref": "pkg:npm/unified-ble-manager@4.0.13",
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.11"
7
+ "version": "4.0.13"
8
8
  },
9
9
  "source": {
10
10
  "method": "pnpm-lock production graph with installed-manifest license audit",
@@ -571,7 +571,8 @@ class UnifiedBleProtocolAndroidDispatcher(
571
571
  endpoint.serviceOccurrence,
572
572
  endpoint.characteristicUuid,
573
573
  endpoint.characteristicOccurrence,
574
- enable
574
+ enable,
575
+ subscriptionType = command.optionalString(21)
575
576
  ) { result ->
576
577
  result.fold(
577
578
  onSuccess = {
@@ -598,7 +599,11 @@ class UnifiedBleProtocolAndroidDispatcher(
598
599
  }
599
600
  val subscriptionId = command.requiredString(7)
600
601
  if (enable) {
601
- activeSubscriptions[subscriptionId] = SubscriptionRoute(subscriptionId, endpoint)
602
+ activeSubscriptions[subscriptionId] = SubscriptionRoute(
603
+ subscriptionId,
604
+ endpoint,
605
+ command.optionalString(21)
606
+ )
602
607
  } else {
603
608
  activeSubscriptions.remove(subscriptionId)
604
609
  }
@@ -682,7 +687,8 @@ class UnifiedBleProtocolAndroidDispatcher(
682
687
  route.endpoint.serviceOccurrence,
683
688
  route.endpoint.characteristicUuid,
684
689
  route.endpoint.characteristicOccurrence,
685
- true
690
+ true,
691
+ subscriptionType = route.mode
686
692
  ) { result ->
687
693
  result.exceptionOrNull()?.let { error ->
688
694
  radio.reportCleanupFailure(
@@ -1100,7 +1106,8 @@ class UnifiedBleProtocolAndroidDispatcher(
1100
1106
 
1101
1107
  private data class SubscriptionRoute(
1102
1108
  val subscriptionId: String,
1103
- val endpoint: CharacteristicEndpoint
1109
+ val endpoint: CharacteristicEndpoint,
1110
+ val mode: String?
1104
1111
  ) {
1105
1112
  fun matches(
1106
1113
  deviceId: String,
@@ -22,10 +22,10 @@ import com.facebook.react.bridge.Arguments;
22
22
  import com.facebook.react.bridge.Promise;
23
23
  import com.facebook.react.bridge.ReactApplicationContext;
24
24
  import com.facebook.react.bridge.ReadableMap;
25
- import com.facebook.react.bridge.RuntimeExecutor;
26
25
  import com.facebook.react.bridge.WritableMap;
27
26
  import com.facebook.react.bridge.ActivityEventListener;
28
27
  import com.facebook.react.module.annotations.ReactModule;
28
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
29
29
  import com.sfourdrinier.unifiedblemanager.NativeUnifiedBleProtocolControlSpec;
30
30
 
31
31
  import java.io.ByteArrayOutputStream;
@@ -487,11 +487,12 @@ public final class UnifiedBleProtocolControlModule extends NativeUnifiedBleProto
487
487
  public synchronized void installExecutionRuntime(Promise promise) {
488
488
  try {
489
489
  requireOpen();
490
- final RuntimeExecutor runtimeExecutor = reactContext.getCatalystInstance().getRuntimeExecutor();
491
- if (runtimeExecutor == null) {
492
- throw new IllegalStateException("React Native RuntimeExecutor is unavailable");
490
+ final CallInvokerHolderImpl jsCallInvokerHolder =
491
+ (CallInvokerHolderImpl) reactContext.getJSCallInvokerHolder();
492
+ if (jsCallInvokerHolder == null) {
493
+ throw new IllegalStateException("React Native JS CallInvoker is unavailable");
493
494
  }
494
- UnifiedBleProtocolJsiBinding.install(runtimeExecutor, nativeHandle, reactContext);
495
+ UnifiedBleProtocolJsiBinding.install(jsCallInvokerHolder, nativeHandle, reactContext);
495
496
  promise.resolve(null);
496
497
  } catch (RuntimeException error) {
497
498
  Log.e(TAG, "installExecutionRuntime failed", error);
@@ -2,8 +2,8 @@
2
2
 
3
3
  package com.sfourdrinier.unifiedblemanager.protocol;
4
4
 
5
- import com.facebook.react.bridge.RuntimeExecutor;
6
5
  import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
7
7
 
8
8
  import java.util.concurrent.ConcurrentHashMap;
9
9
 
@@ -15,14 +15,14 @@ final class UnifiedBleProtocolJsiBinding {
15
15
  private UnifiedBleProtocolJsiBinding() {}
16
16
 
17
17
  static void install(
18
- RuntimeExecutor runtimeExecutor,
18
+ CallInvokerHolderImpl jsCallInvokerHolder,
19
19
  long nativeHandle,
20
20
  ReactApplicationContext context) {
21
21
  final UnifiedBleProtocolAndroidDispatcher dispatcher =
22
22
  DISPATCHERS.reserve(
23
23
  nativeHandle, () -> new UnifiedBleProtocolAndroidDispatcher(context, nativeHandle));
24
24
  try {
25
- installNative(runtimeExecutor, nativeHandle);
25
+ installNative(jsCallInvokerHolder, nativeHandle);
26
26
  } catch (RuntimeException error) {
27
27
  try {
28
28
  close(nativeHandle);
@@ -197,7 +197,7 @@ final class UnifiedBleProtocolJsiBinding {
197
197
  }
198
198
  }
199
199
 
200
- private static native void installNative(RuntimeExecutor runtimeExecutor, long nativeHandle);
200
+ private static native void installNative(CallInvokerHolderImpl jsCallInvokerHolder, long nativeHandle);
201
201
  private static native void uninstallNative(long nativeHandle);
202
202
  private static native String requestCancellationNative(long nativeHandle, long dispatchEpoch, String nonce);
203
203
  private static native boolean emitRecordNative(long nativeHandle, byte[] encodedRecord);
@@ -3,7 +3,7 @@
3
3
  package com.sfourdrinier.unifiedblemanager.protocol.generated
4
4
 
5
5
  const val NATIVE_PROTOCOL_VERSION: Int = 2
6
- const val NATIVE_PROTOCOL_ABI_VERSION: Int = 6
6
+ const val NATIVE_PROTOCOL_ABI_VERSION: Int = 7
7
7
  const val NATIVE_PROTOCOL_CONTROL_SURFACE_VERSION: Int = 2
8
8
  const val MAXIMUM_CONTROL_RECORD_BYTES: Int = 262144
9
9
  const val MAXIMUM_BINARY_PAYLOAD_BYTES: Int = 524288
@@ -119,6 +119,11 @@ enum class WriteModes(val wireValue: Int) {
119
119
  WITHOUT_RESPONSE(2)
120
120
  }
121
121
 
122
+ enum class NotificationDeliveryModes(val wireValue: Int) {
123
+ NOTIFICATION(1),
124
+ INDICATION(2)
125
+ }
126
+
122
127
  enum class ConnectionPriorities(val wireValue: Int) {
123
128
  LOW_POWER(1),
124
129
  BALANCED(2),
@@ -271,6 +276,7 @@ val NATIVE_PROTOCOL_FIELDS: List<FieldDescriptor> = listOf(
271
276
  FieldDescriptor(RecordKind.COMMAND, 18, "phyRx", "enum:connectionPhys", false),
272
277
  FieldDescriptor(RecordKind.COMMAND, 19, "pairTransport", "enum:pairTransports", false),
273
278
  FieldDescriptor(RecordKind.COMMAND, 20, "connectionIntent", "enum:connectionIntents", false),
279
+ FieldDescriptor(RecordKind.COMMAND, 21, "notificationDeliveryMode", "enum:notificationDeliveryModes", false),
274
280
  FieldDescriptor(RecordKind.TERMINAL, 1, "correlation", "record:operationCorrelation", true),
275
281
  FieldDescriptor(RecordKind.TERMINAL, 2, "outcome", "enum:terminalOutcomes", true),
276
282
  FieldDescriptor(RecordKind.TERMINAL, 3, "cause", "string", false),
@@ -6,7 +6,7 @@
6
6
  #include <android/log.h>
7
7
  #include <fbjni/fbjni.h>
8
8
  #include <jsi/jsi.h>
9
- #include <react/jni/JRuntimeExecutor.h>
9
+ #include <ReactCommon/CallInvokerHolder.h>
10
10
 
11
11
  #include <algorithm>
12
12
  #include <cstddef>
@@ -1302,22 +1302,27 @@ class UnifiedBleProtocolJsiBinding final : public jni::JavaClass<UnifiedBleProto
1302
1302
 
1303
1303
  static void installNative(
1304
1304
  jni::alias_ref<jclass>,
1305
- jni::alias_ref<facebook::react::JRuntimeExecutor::javaobject> runtimeExecutor,
1305
+ jni::alias_ref<facebook::react::CallInvokerHolder::javaobject> jsCallInvokerHolder,
1306
1306
  jlong nativeHandle) {
1307
1307
  const auto runtimeLease = unifiedBleProtocolRuntimeLease(nativeHandle);
1308
1308
  if (runtimeLease.expired()) {
1309
1309
  throw std::invalid_argument("Native Protocol v2 runtime is unavailable");
1310
1310
  }
1311
- auto executor = runtimeExecutor->cthis()->get();
1311
+ auto callInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();
1312
+ if (!callInvoker) {
1313
+ throw std::invalid_argument("React Native JS CallInvoker is unavailable");
1314
+ }
1312
1315
  auto state = std::make_shared<JsiEventSinkState>(
1313
1316
  runtimeLease,
1314
- [executor](std::function<void(jsi::Runtime&)> task) { executor(std::move(task)); },
1317
+ [callInvoker](std::function<void(jsi::Runtime&)> task) {
1318
+ callInvoker->invokeAsync(std::move(task));
1319
+ },
1315
1320
  nativeHandle);
1316
1321
  {
1317
1322
  std::scoped_lock lock(eventSinkStatesMutex);
1318
1323
  eventSinkStates[nativeHandle] = state;
1319
1324
  }
1320
- executor([runtimeLease, nativeHandle, state](jsi::Runtime& runtime) {
1325
+ callInvoker->invokeAsync([runtimeLease, nativeHandle, state](jsi::Runtime& runtime) {
1321
1326
  runtime.global().setProperty(
1322
1327
  runtime,
1323
1328
  kRuntimeName,
@@ -741,6 +741,21 @@ class UnifiedBleProtocolAndroidDispatcherLifecycleTest {
741
741
  )
742
742
  }
743
743
 
744
+ @Test
745
+ fun dispatcherForwardsResolvedSubscriptionModeToOwnedRadioCccdSelection() {
746
+ val dispatcher = readAndroidSource(
747
+ "android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt"
748
+ )
749
+ val radio = readAndroidSource(
750
+ "android/src/main/java/com/sfourdrinier/unifiedblemanager/radio/OwnedAndroidGattRadio.kt"
751
+ )
752
+
753
+ assertTrue(dispatcher.contains("subscriptionType = command.optionalString(21)"))
754
+ assertTrue(dispatcher.contains("command.optionalString(21)\n )"))
755
+ assertTrue(dispatcher.contains("subscriptionType = route.mode"))
756
+ assertTrue(radio.contains("resolveCccdPayload(enable, subscriptionType"))
757
+ }
758
+
744
759
  @Test
745
760
  fun activeCancellationKeepsTheFifoBlockedUntilTheLateNativeCompletion() {
746
761
  val scheduled = ArrayDeque<() -> Unit>()
package/docs/AGENTS.md ADDED
@@ -0,0 +1,33 @@
1
+ # AGENTS.md — docs/
2
+
3
+ Conventions for the documentation tree. Root [`AGENTS.md`](../AGENTS.md) still
4
+ applies; this file adds only what is specific to `docs/`.
5
+
6
+ ## The index is load-bearing
7
+
8
+ [`docs/README.md`](README.md) maps every document with a status of **Current**,
9
+ **Historical**, or **Generated**. When you add, move, or retire a document,
10
+ update the index in the same change. `pnpm docs:check` (via
11
+ `scripts/docs/check-docs-index.js`) fails when the index misses a document,
12
+ links to a missing one, or a row lacks a status — and when `llms.txt` stops
13
+ covering a public entrypoint.
14
+
15
+ ## Historical documents are records, not guidance
16
+
17
+ Anything marked Historical (fix trackers, audits, delivered plans, lineage
18
+ docs) is frozen evidence. Never act on its content, never "refresh" it; the
19
+ only acceptable edits are status banners and link repairs. If a grep lands you
20
+ in one, go back to the index and find the Current document instead.
21
+
22
+ ## Generated documents are owned by generators
23
+
24
+ `docs/generated/*`, `docs/index.html`, and `docs/assets/*` are generator
25
+ output. Change the source of truth, run the generator (`pnpm docs:backend-sdk`,
26
+ `pnpm docs`), and commit its output. Verify with `pnpm docs:check`.
27
+
28
+ ## Teaching prose is tested
29
+
30
+ `__tests__/Docs.*.test.js` guard the consumer-facing documents: code fences
31
+ must use the real public API, and known-wrong patterns are rejected. If you
32
+ change a recipe, run `pnpm test:package` — a failing docs test means the prose
33
+ and the contract disagree, and the contract wins.
package/docs/ELECTRON.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Main owns the radio. The renderer uses a versioned IPC client and never loads a native addon.
6
6
 
7
- This source targets `4.0.11`. Tagged releases ship Node-API v8 prebuilds covering macOS and Windows `arm64`/`x64` for both Node and modern Electron.
7
+ This source targets `4.0.13`. Tagged releases ship Node-API v8 prebuilds covering macOS and Windows `arm64`/`x64` for both Node and modern Electron.
8
8
 
9
9
  `unified-ble-manager/electron/main` and
10
10
  `unified-ble-manager/electron/renderer` are the only Electron entrypoints.
@@ -7,7 +7,7 @@ start a radio, request runtime permissions during prebuild, or prove physical
7
7
  radio/restoration reliability. Expo Go is not a supported BLE execution
8
8
  environment because it cannot contain this native module.
9
9
 
10
- Use the v2 plugin options in this `4.0.11` source. Those options match
10
+ Use the v2 plugin options in this `4.0.13` source. Those options match
11
11
  the schema introduced at `4.0.0-rc.4`. Expo Go cannot load this native module.
12
12
 
13
13
  ## Installation and development build
package/docs/GAPS.4.0.md CHANGED
@@ -14,7 +14,7 @@ This file tracks platform code, CI, package, lab, and live-radio evidence. It do
14
14
 
15
15
  The clean-baseline contract, unified core, public manager, deterministic backend, TCK, native protocol, first-party backend implementations, host-isolated package exports, SDK/CLI, and legacy-absence gates exist in the 4.0 source. Passing deterministic, compile, ABI, or package tests are implementation proof; they do not become physical-radio support evidence unless a retained record proves the corresponding live scenario.
16
16
 
17
- This source targets `unified-ble-manager@4.0.11`; the npm registry and release
17
+ This source targets `unified-ble-manager@4.0.13`; the npm registry and release
18
18
  provenance, not a source version string, determine whether it is published.
19
19
  Earlier stable releases remain immutable published history. Backend support labels remain
20
20
  evidence-derived. This package does not rewrite the evidence inventory: a backend remains at the support level
@@ -4,7 +4,7 @@
4
4
 
5
5
  This page gets you to a first scan, connect, read, notify, and teardown on React Native. Other hosts are linked at the bottom. The root import does not turn Bluetooth on.
6
6
 
7
- This source targets `4.0.11`; verify the published version in the npm registry.
7
+ This source targets `4.0.13`; verify the published version in the npm registry.
8
8
 
9
9
  ## Pick a host
10
10
 
@@ -38,11 +38,11 @@ request runtime permissions on Android 12+, add
38
38
  #### Expo / CNG v2
39
39
 
40
40
  The Expo v2 schema and `unified-ble-manager/expo` factory are in this source.
41
- After the npm registry lists `4.0.11`, install that exact version and keep it in
41
+ After the npm registry lists `4.0.13`, install that exact version and keep it in
42
42
  your lockfile while validating the native build:
43
43
 
44
44
  ```sh
45
- pnpm add unified-ble-manager@4.0.11
45
+ pnpm add unified-ble-manager@4.0.13
46
46
  ```
47
47
 
48
48
  The package does not run in Expo Go.
package/docs/NODE.md CHANGED
@@ -10,7 +10,7 @@ The root import does not open an adapter. Pick one backend:
10
10
  | `unified-ble-manager/node/winrt` | Windows |
11
11
  | `unified-ble-manager/node/bluez` | Linux (needs `dbus-next@^0.10.2` in the app) |
12
12
 
13
- This source targets `4.0.11`. Tagged releases ship Node-API v8 prebuilds for macOS and Windows on `arm64` and `x64`. A normal install should not compile native code. BlueZ talks D-Bus and has no addon.
13
+ This source targets `4.0.13`. Tagged releases ship Node-API v8 prebuilds for macOS and Windows on `arm64` and `x64`. A normal install should not compile native code. BlueZ talks D-Bus and has no addon.
14
14
 
15
15
  ## One-call factories
16
16