diff --git a/CHANGELOG.md b/CHANGELOG.md index 2284e018..1dc9a8ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.1 +* Bump `flutter_web_bluetooth` to `^1.2.0`. +* Web: use the dependency's advertisement abort controller and `startupTimeout` instead of the hand-rolled abort/timeout workaround. `unwatchAdvertisements` now also aborts a pending advertisement startup. + ## 3.0.0 * **Breaking:** Add `QueueType.auto` which auto-selects the best queueing strategy per platform: Android, Web, and Linux use a per-device queue (their BLE stacks reject or misbehave on overlapping operations), while all other platforms run commands in parallel. It is now the default for both `UniversalBle` and `UniversalBlePeripheral`, replacing the previous `QueueType.global` default. Any exhaustive `switch` over `QueueType` must handle the new `auto` value. * **Breaking:** Device IDs are now emitted in lower-case on every platform (scan results, connection/value/pairing/connection-parameter callbacks and streams). Previously each platform reported its native case — Android upper-cased MACs, Windows/WinRT lower-cased them. IDs are now canonicalised to lower-case throughout the Dart layer; the native side converts back to the case it requires at its boundary (Android's `getRemoteDevice` needs upper-case). Callers that stored or compared an emitted ID by exact case (e.g. an Android upper-case MAC) must now lower-case it, or compare case-insensitively. Web is exempt: Web Bluetooth IDs are opaque, case-sensitive browser tokens rather than addresses, so they are emitted and matched verbatim. Follow-up to the case-insensitive matching in 2.1.1. Custom `UniversalBlePlatform` implementations must report the canonical form too: the event-stream filters now match the canonical id directly, so a platform that pushes a non-canonical id straight onto the exported stream controllers (instead of calling the `update*` handlers, which canonicalise) will no longer match a query made with a different casing. The same applies to peripheral mode: central device IDs in `UniversalBlePeripheral` streams, read/write request handlers, and `getSubscribedClients` results are emitted in lower-case, and IDs passed to `updateCharacteristicValue` / `getMaximumNotifyLength` are accepted in any case. diff --git a/example/pubspec.lock b/example/pubspec.lock index 8285cdf0..1b7ed93f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -172,10 +172,10 @@ packages: dependency: transitive description: name: flutter_web_bluetooth - sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8 + sha256: "71fa37f699032b4416d56aeb5214363ba057f6aed4eba544cc26293e2582b85d" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter diff --git a/hil/pubspec.lock b/hil/pubspec.lock index ec0861b3..66a58d42 100644 --- a/hil/pubspec.lock +++ b/hil/pubspec.lock @@ -116,10 +116,10 @@ packages: dependency: transitive description: name: flutter_web_bluetooth - sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8 + sha256: "71fa37f699032b4416d56aeb5214363ba057f6aed4eba544cc26293e2582b85d" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index 14b7802a..d726bac4 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -3,9 +3,6 @@ import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:flutter_web_bluetooth/flutter_web_bluetooth.dart'; -import 'package:flutter_web_bluetooth/js_web_bluetooth.dart' - show WatchAdvertisementsOptions; -import 'package:flutter_web_bluetooth/web/js/js.dart' show AbortController; import 'package:universal_ble/src/utils/universal_logger.dart'; import 'package:universal_ble/universal_ble.dart'; @@ -26,8 +23,6 @@ class UniversalBleWeb extends UniversalBlePlatform { final Map _bluetoothDeviceList = {}; final Map _deviceAdvertisementStreamList = {}; - final Map> _advertisementStartOperations = {}; - final Map _advertisementControllers = {}; static const _advertisementTimeout = Duration(seconds: 5); final Map _connectedDeviceStreamList = {}; final Map _characteristicStreamList = {}; @@ -338,28 +333,11 @@ class UniversalBleWeb extends UniversalBlePlatform { ); }); device.advertisementsUseMemory = true; - // Own the abort signal: the dependency does not retain its controller - // when watchAdvertisements is called without a timeout. A lifetime - // timeout would also stop a successfully started scan after five seconds. - final controller = AbortController(); - _advertisementControllers[device.id] = controller; - // The wrapper has no API accepting a caller-owned abort signal. - // ignore: deprecated_member_use - final operation = device.nativeDevice - .watchAdvertisements( - WatchAdvertisementsOptions(signal: controller.signal)) - .timeout(_advertisementTimeout, onTimeout: () { - controller.abort(); - throw TimeoutException('Advertisement startup timed out'); - }); - _advertisementStartOperations[device.id] = operation; - try { - await operation; - } finally { - if (identical(_advertisementStartOperations[device.id], operation)) { - _advertisementStartOperations.remove(device.id); - } - } + // Watch indefinitely but bound only the startup. The dependency keeps its + // own abort controller for the whole watch, so `unwatchAdvertisements` + // aborts a pending start as well as a running watch, and `startupTimeout` + // expires without stopping a scan that started successfully. + await device.watchAdvertisements(null, _advertisementTimeout); } catch (e) { UniversalLogger.logError("WebWatchAdvertisementError: $e"); } @@ -727,17 +705,12 @@ class UniversalBleWeb extends UniversalBlePlatform { for (final id in deviceIds) { final subscription = _deviceAdvertisementStreamList[id]; final device = _getDeviceById(id); - // Abort pending startup before waiting for it. An aborted signal also - // prevents a late native completion from restarting advertisements. - _advertisementControllers.remove(id)?.abort(); await subscription?.cancel(); - try { - await _advertisementStartOperations[id]; - } catch (_) { - // Advertisement startup reports its own error and is optional. - } - // Let unexpected stop failures propagate: connecting while the browser - // is still watching would recreate the scan/connect overlap. + // `unwatchAdvertisements` aborts a pending startup before stopping a + // running watch, which also prevents a late native completion from + // restarting advertisements. Let unexpected stop failures propagate: + // connecting while the browser is still watching would recreate the + // scan/connect overlap. try { await device?.unwatchAdvertisements().timeout(_advertisementTimeout); } finally { diff --git a/pubspec.yaml b/pubspec.yaml index 6445e1d4..c2096787 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: universal_ble description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter -version: 3.0.0 +version: 3.0.1 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues @@ -31,7 +31,7 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.8 - flutter_web_bluetooth: ^1.1.0 + flutter_web_bluetooth: ^1.2.0 bluez: ^0.8.3 meta: ^1.16.0