Skip to content

Commit 176ab34

Browse files
committed
Add implementation for WebBluetooth
1 parent 21947d6 commit 176ab34

File tree

8 files changed

+625
-0
lines changed

8 files changed

+625
-0
lines changed

Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ libc = "0.2.119"
4949
[target.'cfg(target_os = "windows")'.dependencies]
5050
windows = { version = "0.33.0", features = ["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.5"
5475
pretty_env_logger = "0.4.0"

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

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)