Skip to content

Commit f7331af

Browse files
committed
Add implementation for WebBluetooth
1 parent 76fd795 commit f7331af

File tree

8 files changed

+621
-3
lines changed

8 files changed

+621
-3
lines changed

Cargo.toml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ thiserror = "1.0.30"
3131
uuid = "0.8.2"
3232
serde_cr = { package = "serde", version = "1.0.133", features = ["derive"], default-features = false, optional = true }
3333
serde_bytes = { version = "0.11.5", optional = true }
34-
dashmap = "5.0.0"
34+
dashmap = "4.0.2"
3535
futures = "0.3.19"
3636
static_assertions = "1.1.0"
3737
tokio = { version = "1.15.0", features = ["rt", "sync"] }
3838
tokio-stream = { version = "0.1.8", features = ["sync"] }
3939

4040
[target.'cfg(target_os = "linux")'.dependencies]
4141
dbus = "0.9.5"
42-
bluez-async = "0.5.5"
42+
bluez-async = { version = "0.5.5", optional = true }
4343

4444
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
4545
cocoa = "0.24.0"
@@ -49,9 +49,29 @@ libc = "0.2.112"
4949
[target.'cfg(target_os = "windows")'.dependencies]
5050
windows = { version = "0.28.0", features = ["std", "Devices_Bluetooth", "Devices_Bluetooth_GenericAttributeProfile", "Devices_Bluetooth_Advertisement", "Devices_Radios", "Foundation_Collections", "Foundation", "Storage_Streams"] }
5151

52+
[target.'cfg(target_arch = "wasm32")'.dependencies]
53+
js-sys = "0.3.56"
54+
wasm-bindgen = "0.2.79"
55+
wasm-bindgen-futures = "0.4.29"
56+
57+
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
58+
version = "0.3.4"
59+
features = [
60+
'Bluetooth',
61+
'BluetoothCharacteristicProperties',
62+
'BluetoothDevice',
63+
'BluetoothLeScanFilterInit',
64+
'BluetoothRemoteGattCharacteristic',
65+
'BluetoothRemoteGattServer',
66+
'BluetoothRemoteGattService',
67+
'Event',
68+
'Navigator',
69+
'RequestDeviceOptions',
70+
'Window',
71+
]
72+
5273
[dev-dependencies]
5374
rand = "0.8.4"
5475
pretty_env_logger = "0.4.0"
5576
tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread"] }
5677
serde_json = "1.0.74"
57-

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ mod corebluetooth;
105105
pub mod platform;
106106
#[cfg(feature = "serde")]
107107
pub mod serde;
108+
#[cfg(target_arch = "wasm32")]
109+
mod wasm;
108110
#[cfg(target_os = "windows")]
109111
mod winrtble;
110112

@@ -132,6 +134,9 @@ pub enum Error {
132134
#[error("Invalid Bluetooth address: {0}")]
133135
InvalidBDAddr(#[from] ParseBDAddrError),
134136

137+
#[error("JavaScript {:?}", _0)]
138+
JavaScript(String),
139+
135140
#[error("{}", _0)]
136141
Other(Box<dyn std::error::Error + Send + Sync>),
137142
}

src/platform.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ pub use crate::bluez::{
99
pub use crate::corebluetooth::{
1010
adapter::Adapter, manager::Manager, peripheral::Peripheral, peripheral::PeripheralId,
1111
};
12+
#[cfg(target_arch = "wasm32")]
13+
pub use crate::wasm::{
14+
adapter::Adapter, manager::Manager, peripheral::Peripheral, peripheral::PeripheralId,
15+
};
1216
#[cfg(target_os = "windows")]
1317
pub use crate::winrtble::{
1418
adapter::Adapter, manager::Manager, peripheral::Peripheral, peripheral::PeripheralId,

src/wasm/adapter.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use super::peripheral::{Peripheral, PeripheralId};
2+
use super::utils::wrap_promise;
3+
use crate::api::{BDAddr, Central, CentralEvent, Peripheral as _, ScanFilter};
4+
use crate::common::adapter_manager::AdapterManager;
5+
use crate::{Error, Result};
6+
use async_trait::async_trait;
7+
use futures::channel::oneshot;
8+
use futures::stream::Stream;
9+
use js_sys::Array;
10+
use std::pin::Pin;
11+
use std::sync::Arc;
12+
use wasm_bindgen::prelude::*;
13+
use wasm_bindgen_futures::spawn_local;
14+
use web_sys::{BluetoothDevice, BluetoothLeScanFilterInit, RequestDeviceOptions};
15+
16+
/// Implementation of [api::Central](crate::api::Central).
17+
#[derive(Clone, Debug)]
18+
pub struct Adapter {
19+
manager: Arc<AdapterManager<Peripheral>>,
20+
}
21+
22+
fn bluetooth() -> Option<web_sys::Bluetooth> {
23+
web_sys::window().unwrap().navigator().bluetooth()
24+
}
25+
26+
trait AddPeripheralAndEmit {
27+
fn add_peripheral_and_emit(&self, device: JsValue);
28+
}
29+
30+
impl AddPeripheralAndEmit for Arc<AdapterManager<Peripheral>> {
31+
fn add_peripheral_and_emit(&self, device: JsValue) {
32+
let p = Peripheral::new(Arc::downgrade(self), BluetoothDevice::from(device));
33+
let id = p.id();
34+
if self.peripheral(&id).is_none() {
35+
self.add_peripheral(p);
36+
self.emit(CentralEvent::DeviceDiscovered(id.into()));
37+
}
38+
}
39+
}
40+
41+
impl Adapter {
42+
pub(crate) fn try_new() -> Option<Self> {
43+
if let Some(_) = bluetooth() {
44+
Some(Self {
45+
manager: Arc::new(AdapterManager::default()),
46+
})
47+
} else {
48+
None
49+
}
50+
}
51+
}
52+
53+
#[async_trait]
54+
impl Central for Adapter {
55+
type Peripheral = Peripheral;
56+
57+
async fn events(&self) -> Result<Pin<Box<dyn Stream<Item = CentralEvent> + Send>>> {
58+
Ok(self.manager.event_stream())
59+
}
60+
61+
async fn start_scan(&self, filter: ScanFilter) -> Result<()> {
62+
let (sender, receiver) = oneshot::channel();
63+
let manager = self.manager.clone();
64+
spawn_local(async move {
65+
// add already found devices
66+
if manager.peripherals().is_empty() {
67+
if let Ok(devices) = wrap_promise::<Array>(bluetooth().unwrap().get_devices()).await
68+
{
69+
for device in devices.iter() {
70+
manager.add_peripheral_and_emit(device);
71+
}
72+
}
73+
}
74+
75+
let mut options = RequestDeviceOptions::new();
76+
let optional_services = Array::new();
77+
let filters = Array::new();
78+
79+
for uuid in filter.services.iter() {
80+
let mut filter = BluetoothLeScanFilterInit::new();
81+
let filter_services = Array::new();
82+
filter_services.push(&uuid.to_string().into());
83+
filter.services(&filter_services.into());
84+
filters.push(&filter.into());
85+
optional_services.push(&uuid.to_string().into());
86+
}
87+
88+
options.filters(&filters.into());
89+
options.optional_services(&optional_services.into());
90+
91+
let ret = wrap_promise(bluetooth().unwrap().request_device(&options))
92+
.await
93+
.map(|device| {
94+
manager.add_peripheral_and_emit(device);
95+
()
96+
});
97+
98+
let _ = sender.send(ret);
99+
});
100+
101+
receiver.await.unwrap()
102+
}
103+
104+
async fn stop_scan(&self) -> Result<()> {
105+
Ok(())
106+
}
107+
108+
async fn peripherals(&self) -> Result<Vec<Peripheral>> {
109+
Ok(self.manager.peripherals())
110+
}
111+
112+
async fn peripheral(&self, id: &PeripheralId) -> Result<Peripheral> {
113+
self.manager.peripheral(id).ok_or(Error::DeviceNotFound)
114+
}
115+
116+
async fn add_peripheral(&self, _address: BDAddr) -> Result<Peripheral> {
117+
Err(Error::NotSupported(
118+
"Can't add a Peripheral from a BDAddr".to_string(),
119+
))
120+
}
121+
122+
async fn adapter_info(&self) -> Result<String> {
123+
Ok("WebBluetooth".to_string())
124+
}
125+
}

src/wasm/manager.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use super::adapter::Adapter;
2+
use crate::{api, Result};
3+
use async_trait::async_trait;
4+
5+
/// Implementation of [api::Manager](crate::api::Manager).
6+
#[derive(Clone, Debug)]
7+
pub struct Manager {}
8+
9+
impl Manager {
10+
pub async fn new() -> Result<Self> {
11+
Ok(Self {})
12+
}
13+
}
14+
15+
#[async_trait]
16+
impl api::Manager for Manager {
17+
type Adapter = Adapter;
18+
19+
async fn adapters(&self) -> Result<Vec<Adapter>> {
20+
if let Some(adapter) = Adapter::try_new() {
21+
Ok(vec![adapter])
22+
} else {
23+
Ok(vec![])
24+
}
25+
}
26+
}

src/wasm/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod adapter;
2+
pub mod manager;
3+
pub mod peripheral;
4+
mod utils;

0 commit comments

Comments
 (0)