unified-ble-manager 4.0.15 → 4.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/MIGRATION_4.0.md +1 -1
- package/README.md +1 -1
- package/RELEASE.md +23 -2
- package/SBOM.cdx.json +4 -4
- package/THIRD_PARTY_LICENSES.json +1 -1
- package/android/src/main/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcher.kt +39 -5
- package/android/src/main/java/com/sfourdrinier/unifiedblemanager/radio/OwnedAndroidGattRadio.kt +31 -3
- package/android/src/test/java/com/sfourdrinier/unifiedblemanager/protocol/UnifiedBleProtocolAndroidDispatcherLifecycleTest.kt +31 -0
- package/docs/ELECTRON.md +1 -1
- package/docs/EXPO_PLUGIN.md +1 -1
- package/docs/GAPS.4.0.md +1 -1
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/NODE.md +1 -1
- package/docs/WEB.md +1 -1
- package/docs/generated/PLATFORM_SUPPORT.md +1 -1
- package/lib/commonjs/backends/corebluetooth/corebluetooth-backend.js +14 -4
- package/lib/commonjs/backends/corebluetooth/corebluetooth-backend.js.map +1 -1
- package/lib/commonjs/backends/corebluetooth/corebluetooth-late-connect-cleanup.js +1 -1
- package/lib/commonjs/backends/corebluetooth/corebluetooth-late-connect-cleanup.js.map +1 -1
- package/lib/commonjs/backends/corebluetooth/corebluetooth-operation-lifecycle.js +17 -3
- package/lib/commonjs/backends/corebluetooth/corebluetooth-operation-lifecycle.js.map +1 -1
- package/lib/commonjs/implementation-version.js +1 -1
- package/lib/commonjs/native-protocol/rn-android-boundary.js +11 -3
- package/lib/commonjs/native-protocol/rn-android-boundary.js.map +1 -1
- package/lib/module/backends/corebluetooth/corebluetooth-backend.js +14 -4
- package/lib/module/backends/corebluetooth/corebluetooth-backend.js.map +1 -1
- package/lib/module/backends/corebluetooth/corebluetooth-late-connect-cleanup.js +1 -1
- package/lib/module/backends/corebluetooth/corebluetooth-late-connect-cleanup.js.map +1 -1
- package/lib/module/backends/corebluetooth/corebluetooth-operation-lifecycle.js +17 -3
- package/lib/module/backends/corebluetooth/corebluetooth-operation-lifecycle.js.map +1 -1
- package/lib/module/implementation-version.js +1 -1
- package/lib/module/native-protocol/rn-android-boundary.js +11 -3
- package/lib/module/native-protocol/rn-android-boundary.js.map +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-backend.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-operation-lifecycle.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-operation-lifecycle.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/winrt/winrt-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/implementation-version.d.ts +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-backend.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-operation-lifecycle.d.ts +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-operation-lifecycle.d.ts.map +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/winrt/winrt-provider.d.ts +1 -1
- package/lib/typescript/module/src/implementation-version.d.ts +1 -1
- package/native/PREBUILDS.json +5 -5
- package/native/electron/corebluetooth/prebuilds/darwin-arm64/unified_ble_corebluetooth.node +0 -0
- package/native/electron/corebluetooth/prebuilds/darwin-x64/unified_ble_corebluetooth.node +0 -0
- package/native/electron/winrt/prebuilds/win32-arm64/unified_ble_winrt.node +0 -0
- package/native/electron/winrt/prebuilds/win32-x64/unified_ble_winrt.node +0 -0
- package/package.json +1 -1
- package/src/backends/corebluetooth/corebluetooth-backend.ts +18 -4
- package/src/backends/corebluetooth/corebluetooth-late-connect-cleanup.ts +1 -1
- package/src/backends/corebluetooth/corebluetooth-operation-lifecycle.ts +18 -3
- package/src/implementation-version.ts +1 -1
- package/src/native-protocol/rn-android-boundary.ts +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ All notable changes to `unified-ble-manager` are documented here.
|
|
|
6
6
|
|
|
7
7
|
No changes yet.
|
|
8
8
|
|
|
9
|
+
## [4.0.16] - 2026-09-03
|
|
10
|
+
|
|
11
|
+
### Fixes
|
|
12
|
+
|
|
13
|
+
- Normalize an Android CCCD operation that terminates with GATT status 19 as
|
|
14
|
+
`connection.lost` even when it arrives before the native `connectionLost`
|
|
15
|
+
event. Other CCCD failures remain `platform.failure` and retain their native
|
|
16
|
+
GATT status.
|
|
17
|
+
- Cancel a dispatched React Native Android `when-available` connection when
|
|
18
|
+
its caller aborts, releasing the native pending connection so a later
|
|
19
|
+
same-peer retry is admitted without affecting distinct-peer connects.
|
|
20
|
+
|
|
9
21
|
## [4.0.15] - 2026-09-02
|
|
10
22
|
|
|
11
23
|
### Fixes
|
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.
|
|
5
|
+
This source targets `4.0.16`. 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.
|
|
21
|
+
This source tree is versioned `4.0.16`. 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.
|
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.
|
|
84
|
+
once tagged. Stable `4.0.0` through `4.0.15` are immutable. `4.0.16` is the current train head.
|
|
85
85
|
|
|
86
86
|
```sh
|
|
87
87
|
release_candidate=4.0.0-rc.N
|
|
@@ -133,6 +133,27 @@ 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.16
|
|
137
|
+
|
|
138
|
+
The `v4.0.16` tag must identify the exact current `main` commit after canonical
|
|
139
|
+
CI passes. Do not tag the release branch directly.
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
git fetch origin --tags
|
|
143
|
+
git checkout main
|
|
144
|
+
git pull --ff-only origin main
|
|
145
|
+
|
|
146
|
+
test "$(git branch --show-current)" = "main"
|
|
147
|
+
test "$(node -p "require('./package.json').version")" = "4.0.16"
|
|
148
|
+
git diff --exit-code
|
|
149
|
+
git diff --cached --exit-code
|
|
150
|
+
|
|
151
|
+
git tag -a v4.0.16 -m "v4.0.16"
|
|
152
|
+
git push origin v4.0.16
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Before tagging, confirm release-note extraction finds `## [4.0.16]`.
|
|
156
|
+
|
|
136
157
|
## Releasing 4.0.15
|
|
137
158
|
|
|
138
159
|
The `v4.0.15` tag must identify the exact current `main` commit after canonical
|
|
@@ -340,7 +361,7 @@ a green publish job and a package a consumer can actually install are not the
|
|
|
340
361
|
same claim.
|
|
341
362
|
|
|
342
363
|
```sh
|
|
343
|
-
version=4.0.
|
|
364
|
+
version=4.0.16
|
|
344
365
|
|
|
345
366
|
npm view "unified-ble-manager@$version" version
|
|
346
367
|
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.
|
|
9
|
+
"bom-ref": "pkg:npm/unified-ble-manager@4.0.16",
|
|
10
10
|
"name": "unified-ble-manager",
|
|
11
|
-
"version": "4.0.
|
|
11
|
+
"version": "4.0.16",
|
|
12
12
|
"licenses": [
|
|
13
13
|
{
|
|
14
14
|
"expression": "Apache-2.0"
|
|
15
15
|
}
|
|
16
16
|
],
|
|
17
|
-
"purl": "pkg:npm/unified-ble-manager@4.0.
|
|
17
|
+
"purl": "pkg:npm/unified-ble-manager@4.0.16"
|
|
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.
|
|
540
|
+
"ref": "pkg:npm/unified-ble-manager@4.0.16",
|
|
541
541
|
"dependsOn": [
|
|
542
542
|
"pkg:npm/%40babel/runtime@7.29.7",
|
|
543
543
|
"pkg:npm/node-addon-api@8.9.0",
|
|
@@ -11,6 +11,7 @@ import com.sfourdrinier.unifiedblemanager.protocol.generated.ConnectionIntents
|
|
|
11
11
|
import com.sfourdrinier.unifiedblemanager.protocol.generated.RecordKind
|
|
12
12
|
import com.sfourdrinier.unifiedblemanager.radio.OwnedAndroidGattRadio
|
|
13
13
|
import com.sfourdrinier.unifiedblemanager.radio.OwnedRadioTeardownFailure
|
|
14
|
+
import com.sfourdrinier.unifiedblemanager.radio.AndroidGattOperationFailure
|
|
14
15
|
import com.sfourdrinier.unifiedblemanager.radio.BondedPeerSnapshot
|
|
15
16
|
import com.sfourdrinier.unifiedblemanager.radio.nextUuidOccurrence
|
|
16
17
|
import java.util.UUID
|
|
@@ -641,7 +642,9 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
641
642
|
}
|
|
642
643
|
emitSuccess(command, if (enable) "subscribed" else "unsubscribed")
|
|
643
644
|
},
|
|
644
|
-
onFailure = { error ->
|
|
645
|
+
onFailure = { error ->
|
|
646
|
+
emitGattOperationFailure(command, "subscriptionFailed", error, "Android CCCD operation failed")
|
|
647
|
+
}
|
|
645
648
|
)
|
|
646
649
|
}
|
|
647
650
|
radioOperationIds[operationKey(command)] = radioOperationId
|
|
@@ -856,21 +859,44 @@ class UnifiedBleProtocolAndroidDispatcher(
|
|
|
856
859
|
radioOperationIds.remove(operationKey(command))
|
|
857
860
|
}
|
|
858
861
|
|
|
859
|
-
private fun
|
|
862
|
+
private fun emitGattOperationFailure(
|
|
863
|
+
command: ProtocolWireRecord,
|
|
864
|
+
fallbackCode: String,
|
|
865
|
+
error: Throwable,
|
|
866
|
+
fallbackMessage: String
|
|
867
|
+
) {
|
|
868
|
+
emitFailure(
|
|
869
|
+
command,
|
|
870
|
+
androidGattOperationFailureCode(error, fallbackCode),
|
|
871
|
+
error.message ?: fallbackMessage,
|
|
872
|
+
androidGattOperationFailureStatus(error)
|
|
873
|
+
)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private fun emitFailure(
|
|
877
|
+
command: ProtocolWireRecord,
|
|
878
|
+
code: String,
|
|
879
|
+
message: String,
|
|
880
|
+
androidGattStatus: Int? = null
|
|
881
|
+
) {
|
|
860
882
|
val bondedPeerCommand = command.requiredString(3) == "enumerateBondedPeers"
|
|
861
883
|
if (bondedPeerCommand) {
|
|
862
884
|
// Keep failure/cancellation/teardown mutually exclusive with a late success.
|
|
863
885
|
if (!claimExactPendingCommand(pendingCommands, operationKey(command), command)) return
|
|
864
886
|
} else if (!isPending(command)) return
|
|
865
|
-
val
|
|
866
|
-
RecordKind.ERROR,
|
|
867
|
-
mapOf(
|
|
887
|
+
val errorFields = mutableMapOf<Int, ProtocolWireValue>(
|
|
868
888
|
1 to ProtocolWireValue.StringValue(code),
|
|
869
889
|
2 to ProtocolWireValue.StringValue("android"),
|
|
870
890
|
3 to ProtocolWireValue.StringValue(command.requiredString(3)),
|
|
871
891
|
4 to ProtocolWireValue.StringValue("notRetryable"),
|
|
872
892
|
7 to ProtocolWireValue.StringValue(message)
|
|
873
893
|
)
|
|
894
|
+
androidGattStatus?.let { status ->
|
|
895
|
+
errorFields[8] = ProtocolWireValue.SignedIntegerValue(status.toLong())
|
|
896
|
+
}
|
|
897
|
+
val error = ProtocolWireRecord(
|
|
898
|
+
RecordKind.ERROR,
|
|
899
|
+
errorFields
|
|
874
900
|
)
|
|
875
901
|
val result = ProtocolWireRecord(
|
|
876
902
|
RecordKind.RESULT,
|
|
@@ -1211,6 +1237,14 @@ internal fun connectionLostEvent(
|
|
|
1211
1237
|
)
|
|
1212
1238
|
}
|
|
1213
1239
|
|
|
1240
|
+
internal fun androidGattOperationFailureCode(error: Throwable, fallbackCode: String): String {
|
|
1241
|
+
val gattFailure = error as? AndroidGattOperationFailure
|
|
1242
|
+
return if (gattFailure?.isLinkLoss == true) "connectionLost" else fallbackCode
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
internal fun androidGattOperationFailureStatus(error: Throwable): Int? =
|
|
1246
|
+
(error as? AndroidGattOperationFailure)?.gattStatus
|
|
1247
|
+
|
|
1214
1248
|
internal fun databaseChangedEvent(
|
|
1215
1249
|
nativeHandle: Long,
|
|
1216
1250
|
database: ProtocolWireRecord,
|
package/android/src/main/java/com/sfourdrinier/unifiedblemanager/radio/OwnedAndroidGattRadio.kt
CHANGED
|
@@ -109,6 +109,27 @@ internal class OwnedAndroidSubscriptionOwnership<K> {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Preserves the Android GATT callback status when an asynchronous operation
|
|
114
|
+
* terminates. Status 19 means that the peer terminated the link; keeping that
|
|
115
|
+
* distinction typed prevents a CCCD race from being flattened to a generic
|
|
116
|
+
* platform failure before the later connection-state callback arrives.
|
|
117
|
+
*/
|
|
118
|
+
internal class AndroidGattOperationFailure(
|
|
119
|
+
val operation: String,
|
|
120
|
+
val gattStatus: Int
|
|
121
|
+
) : IllegalStateException("$operation status=$gattStatus") {
|
|
122
|
+
val isLinkLoss: Boolean
|
|
123
|
+
get() = gattStatus == ANDROID_GATT_LINK_LOSS_STATUS
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
internal const val ANDROID_GATT_LINK_LOSS_STATUS = 19
|
|
127
|
+
|
|
128
|
+
internal fun classifyAndroidGattOperationFailure(
|
|
129
|
+
operation: String,
|
|
130
|
+
gattStatus: Int
|
|
131
|
+
): AndroidGattOperationFailure = AndroidGattOperationFailure(operation, gattStatus)
|
|
132
|
+
|
|
112
133
|
/** Runtime adapter state derived from Android hardware and granted permissions. */
|
|
113
134
|
internal data class OwnedRadioAdapterProtocolState(
|
|
114
135
|
val availability: String,
|
|
@@ -2479,7 +2500,7 @@ class OwnedAndroidGattRadio(private val context: Context) {
|
|
|
2479
2500
|
} else {
|
|
2480
2501
|
completeExactUnit(
|
|
2481
2502
|
pending,
|
|
2482
|
-
Result.failure(
|
|
2503
|
+
Result.failure(classifyAndroidGattOperationFailure("descriptor-write", status))
|
|
2483
2504
|
)
|
|
2484
2505
|
}
|
|
2485
2506
|
return
|
|
@@ -2494,7 +2515,7 @@ class OwnedAndroidGattRadio(private val context: Context) {
|
|
|
2494
2515
|
} else {
|
|
2495
2516
|
completeExactUnit(
|
|
2496
2517
|
pending,
|
|
2497
|
-
Result.failure(
|
|
2518
|
+
Result.failure(classifyAndroidGattOperationFailure("cccd-write", status))
|
|
2498
2519
|
)
|
|
2499
2520
|
}
|
|
2500
2521
|
return
|
|
@@ -2515,7 +2536,14 @@ class OwnedAndroidGattRadio(private val context: Context) {
|
|
|
2515
2536
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
2516
2537
|
cb?.invoke(Result.success(Unit))
|
|
2517
2538
|
} else {
|
|
2518
|
-
cb?.invoke(
|
|
2539
|
+
cb?.invoke(
|
|
2540
|
+
Result.failure(
|
|
2541
|
+
classifyAndroidGattOperationFailure(
|
|
2542
|
+
if (matchedKey?.startsWith("cccd:") == true) "cccd-write" else "descriptor-write",
|
|
2543
|
+
status
|
|
2544
|
+
)
|
|
2545
|
+
)
|
|
2546
|
+
)
|
|
2519
2547
|
}
|
|
2520
2548
|
}
|
|
2521
2549
|
|
|
@@ -12,6 +12,7 @@ import com.sfourdrinier.unifiedblemanager.radio.BondedPeerSnapshot
|
|
|
12
12
|
import com.sfourdrinier.unifiedblemanager.radio.normalizeBondedPeerSnapshots
|
|
13
13
|
import com.sfourdrinier.unifiedblemanager.radio.bondedPeerAdapterReadiness
|
|
14
14
|
import com.sfourdrinier.unifiedblemanager.radio.requiresImmediateGattTeardownOnAdapterState
|
|
15
|
+
import com.sfourdrinier.unifiedblemanager.radio.classifyAndroidGattOperationFailure
|
|
15
16
|
import org.junit.Assert.assertEquals
|
|
16
17
|
import org.junit.Assert.assertArrayEquals
|
|
17
18
|
import org.junit.Assert.assertFalse
|
|
@@ -31,6 +32,36 @@ import android.bluetooth.BluetoothAdapter
|
|
|
31
32
|
import android.bluetooth.le.ScanSettings
|
|
32
33
|
|
|
33
34
|
class UnifiedBleProtocolAndroidDispatcherLifecycleTest {
|
|
35
|
+
@Test
|
|
36
|
+
fun gattStatus19IsTypedAsLinkLossButOrdinaryCccdFailureIsNot() {
|
|
37
|
+
val linkLoss = classifyAndroidGattOperationFailure("cccd-write", 19)
|
|
38
|
+
assertTrue(linkLoss.isLinkLoss)
|
|
39
|
+
assertEquals(19, linkLoss.gattStatus)
|
|
40
|
+
|
|
41
|
+
val ordinaryFailure = classifyAndroidGattOperationFailure("cccd-write", 133)
|
|
42
|
+
assertFalse(ordinaryFailure.isLinkLoss)
|
|
43
|
+
assertEquals(133, ordinaryFailure.gattStatus)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test
|
|
47
|
+
fun dispatcherOnlyMapsTypedLinkLossToConnectionLost() {
|
|
48
|
+
val linkLoss = classifyAndroidGattOperationFailure("cccd-write", 19)
|
|
49
|
+
assertEquals("connectionLost", androidGattOperationFailureCode(linkLoss, "subscriptionFailed"))
|
|
50
|
+
assertEquals(19, androidGattOperationFailureStatus(linkLoss))
|
|
51
|
+
|
|
52
|
+
val ordinaryFailure = classifyAndroidGattOperationFailure("cccd-write", 133)
|
|
53
|
+
assertEquals("subscriptionFailed", androidGattOperationFailureCode(ordinaryFailure, "subscriptionFailed"))
|
|
54
|
+
assertEquals(133, androidGattOperationFailureStatus(ordinaryFailure))
|
|
55
|
+
|
|
56
|
+
// Matching an error message is intentionally insufficient: only the
|
|
57
|
+
// typed callback failure may change the public terminal vocabulary.
|
|
58
|
+
assertEquals(
|
|
59
|
+
"subscriptionFailed",
|
|
60
|
+
androidGattOperationFailureCode(IllegalStateException("onDescriptorWrite status=19"), "subscriptionFailed")
|
|
61
|
+
)
|
|
62
|
+
assertNull(androidGattOperationFailureStatus(IllegalStateException("onDescriptorWrite status=19")))
|
|
63
|
+
}
|
|
64
|
+
|
|
34
65
|
@Test
|
|
35
66
|
fun bondedPeerAdapterReadinessReturnsTypedFailuresInsteadOfAnEmptySuccess() {
|
|
36
67
|
assertTrue(
|
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.
|
|
7
|
+
This source targets `4.0.16`. 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.
|
package/docs/EXPO_PLUGIN.md
CHANGED
|
@@ -7,7 +7,7 @@ start a radio, request runtime permissions during prebuild, or prove physical
|
|
|
7
7
|
radio/restoration reliability. Expo Go is not a supported BLE execution
|
|
8
8
|
environment because it cannot contain this native module.
|
|
9
9
|
|
|
10
|
-
Use the v2 plugin options in this `4.0.
|
|
10
|
+
Use the v2 plugin options in this `4.0.16` 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.
|
|
17
|
+
This source targets `unified-ble-manager@4.0.16`; 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
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
This page gets you to a first scan, connect, read, notify, and teardown on React Native. Other hosts are linked at the bottom. The root import does not turn Bluetooth on.
|
|
6
6
|
|
|
7
|
-
This source targets `4.0.
|
|
7
|
+
This source targets `4.0.16`; 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.
|
|
41
|
+
After the npm registry lists `4.0.16`, 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.
|
|
45
|
+
pnpm add unified-ble-manager@4.0.16
|
|
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.
|
|
13
|
+
This source targets `4.0.16`. 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
|
|
package/docs/WEB.md
CHANGED
|
@@ -6,7 +6,7 @@ Use `unified-ble-manager/web` to run the public UBM manager directly in a browse
|
|
|
6
6
|
|
|
7
7
|
The complete runnable TypeScript/Vite application is in [`example-web/`](../example-web/).
|
|
8
8
|
|
|
9
|
-
This guide targets `4.0.
|
|
9
|
+
This guide targets `4.0.16`. Web Bluetooth support still depends on the browser, operating system, adapter, and peripheral. UBM reports those runtime boundaries; it does not fabricate a fallback backend.
|
|
10
10
|
|
|
11
11
|
## Requirements
|
|
12
12
|
|
|
@@ -6,7 +6,7 @@ This page is generated by `scripts/docs/generate-platform-support.js` from valid
|
|
|
6
6
|
|
|
7
7
|
## Current package
|
|
8
8
|
|
|
9
|
-
`unified-ble-manager@4.0.
|
|
9
|
+
`unified-ble-manager@4.0.16`
|
|
10
10
|
|
|
11
11
|
No evidence record is bound to this exact package version and artifact. Consequently, this generated page makes no Preview, Live Preview, Supported, or Reliability-qualified platform claim for the current package.
|
|
12
12
|
|
|
@@ -699,6 +699,18 @@ class CoreBluetoothBackend {
|
|
|
699
699
|
this.nextConnection += 1;
|
|
700
700
|
this.nextLease += 1;
|
|
701
701
|
this.connectionsByNativeId.set(nativePeerId, record);
|
|
702
|
+
const cleanupCancelledConnection = async () => {
|
|
703
|
+
// A caller may abort before the boundary has dispatched the native
|
|
704
|
+
// connect. Preserve the quarantine record in that case so a late native
|
|
705
|
+
// success still receives compensation; once native owns the link,
|
|
706
|
+
// releaseLateCoreBluetoothConnection cancels both connecting and
|
|
707
|
+
// connected states through the boundary.
|
|
708
|
+
if (this.boundary.connectionState(nativePeerId) === 'disconnected') return;
|
|
709
|
+
const released = await (0, _corebluetoothLateConnectCleanup.releaseLateCoreBluetoothConnection)(this.boundary, this.connectionsByNativeId, record);
|
|
710
|
+
if (!released) {
|
|
711
|
+
console.error(`${this.diagnosticTag('connect')} Cancelled native connection remains active:`, record.nativePeerId);
|
|
712
|
+
}
|
|
713
|
+
};
|
|
702
714
|
try {
|
|
703
715
|
await this.operationLifecycle.awaitBoundaryOperation(options, 'direct-gatt.connect', () => this.boundary.connect(nativePeerId, intent), async () => {
|
|
704
716
|
const released = await (0, _corebluetoothLateConnectCleanup.releaseLateCoreBluetoothConnection)(this.boundary, this.connectionsByNativeId, record);
|
|
@@ -706,10 +718,8 @@ class CoreBluetoothBackend {
|
|
|
706
718
|
console.error(`${this.diagnosticTag('connect')} Late native connection remains active:`, record.nativePeerId);
|
|
707
719
|
}
|
|
708
720
|
}, async () => {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
}, String(record.connectionId));
|
|
721
|
+
await cleanupCancelledConnection();
|
|
722
|
+
}, String(record.connectionId), cleanupCancelledConnection);
|
|
713
723
|
} catch (error) {
|
|
714
724
|
const terminalCancellation = error instanceof _errors.BackendContractError && (error.normalized.code === 'operation.aborted' || error.normalized.code === 'operation.timed-out' || error.normalized.code === 'operation.cancelled-by-destroy');
|
|
715
725
|
if (!terminalCancellation) {
|