Skip to content

Commit 3c6d529

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

File tree

8 files changed

+646
-3
lines changed

8 files changed

+646
-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.56"
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: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
#[async_trait]
27+
trait AddPeripheralAndEmit {
28+
async fn add_inital_periperals(&self) -> Vec<PeripheralId>;
29+
fn add_device(&self, device: JsValue) -> Option<PeripheralId>;
30+
}
31+
32+
#[async_trait]
33+
impl AddPeripheralAndEmit for Arc<AdapterManager<Peripheral>> {
34+
async fn add_inital_periperals(&self) -> Vec<PeripheralId> {
35+
if !self.peripherals().is_empty() {
36+
return vec![];
37+
}
38+
39+
let self_clone = self.clone();
40+
let (sender, receiver) = oneshot::channel();
41+
spawn_local(async move {
42+
let _ = sender.send(
43+
wrap_promise::<Array>(bluetooth().unwrap().get_devices())
44+
.await
45+
.map_or(vec![], |devices| {
46+
devices
47+
.iter()
48+
.map(|device| self_clone.add_device(device).unwrap())
49+
.collect()
50+
}),
51+
);
52+
});
53+
receiver.await.unwrap()
54+
}
55+
56+
fn add_device(&self, device: JsValue) -> Option<PeripheralId> {
57+
let p = Peripheral::new(Arc::downgrade(self), BluetoothDevice::from(device));
58+
let id = p.id();
59+
if self.peripheral(&id).is_none() {
60+
self.add_peripheral(p);
61+
Some(id)
62+
} else {
63+
None
64+
}
65+
}
66+
}
67+
68+
impl Adapter {
69+
pub(crate) fn try_new() -> Option<Self> {
70+
if let Some(_) = bluetooth() {
71+
Some(Self {
72+
manager: Arc::new(AdapterManager::default()),
73+
})
74+
} else {
75+
None
76+
}
77+
}
78+
}
79+
80+
#[async_trait]
81+
impl Central for Adapter {
82+
type Peripheral = Peripheral;
83+
84+
async fn events(&self) -> Result<Pin<Box<dyn Stream<Item = CentralEvent> + Send>>> {
85+
Ok(self.manager.event_stream())
86+
}
87+
88+
async fn start_scan(&self, filter: ScanFilter) -> Result<()> {
89+
let (sender, receiver) = oneshot::channel();
90+
let manager = self.manager.clone();
91+
spawn_local(async move {
92+
for id in manager.add_inital_periperals().await {
93+
manager.emit(CentralEvent::DeviceDiscovered(id));
94+
}
95+
96+
let mut options = RequestDeviceOptions::new();
97+
let optional_services = Array::new();
98+
let filters = Array::new();
99+
100+
for uuid in filter.services.iter() {
101+
let mut filter = BluetoothLeScanFilterInit::new();
102+
let filter_services = Array::new();
103+
filter_services.push(&uuid.to_string().into());
104+
filter.services(&filter_services.into());
105+
filters.push(&filter.into());
106+
optional_services.push(&uuid.to_string().into());
107+
}
108+
109+
options.filters(&filters.into());
110+
options.optional_services(&optional_services.into());
111+
112+
let ret = wrap_promise(bluetooth().unwrap().request_device(&options))
113+
.await
114+
.map(|device| {
115+
if let Some(id) = manager.add_device(device) {
116+
manager.emit(CentralEvent::DeviceDiscovered(id));
117+
}
118+
()
119+
});
120+
121+
let _ = sender.send(ret);
122+
});
123+
124+
receiver.await.unwrap()
125+
}
126+
127+
async fn stop_scan(&self) -> Result<()> {
128+
Ok(())
129+
}
130+
131+
async fn peripherals(&self) -> Result<Vec<Peripheral>> {
132+
self.manager.add_inital_periperals().await;
133+
Ok(self.manager.peripherals())
134+
}
135+
136+
async fn peripheral(&self, id: &PeripheralId) -> Result<Peripheral> {
137+
self.manager.add_inital_periperals().await;
138+
self.manager.peripheral(id).ok_or(Error::DeviceNotFound)
139+
}
140+
141+
async fn add_peripheral(&self, _address: BDAddr) -> Result<Peripheral> {
142+
Err(Error::NotSupported(
143+
"Can't add a Peripheral from a BDAddr".to_string(),
144+
))
145+
}
146+
147+
async fn adapter_info(&self) -> Result<String> {
148+
Ok("WebBluetooth".to_string())
149+
}
150+
}

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)