Skip to main content

webusb_web/
lib.rs

1//! WebUSB on the web 🕸️ — Access USB devices from the web browser.
2//!
3//! The **WebUSB API** provides a way to expose non-standard Universal Serial Bus (USB)
4//! compatible devices services to the web, to make USB safer and easier to use.
5//!
6//! This crate provides Rust support for using WebUSB when targeting WebAssembly.
7//!
8//! MDN provides a [WebUSB overview] while detailed information is available in the
9//! [WebUSB specification].
10//!
11//! [WebUSB overview]: https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API
12//! [WebUSB specification]: https://wicg.github.io/webusb/
13//!
14//! ### Building
15//! This crate depends on unstable features of the `web_sys` crate.
16//! Therefore you must add `--cfg=web_sys_unstable_apis` to the Rust
17//! compiler flags (`RUSTFLAGS`).
18//!
19//! ### Usage
20//! Call [`Usb::new()`] to obtain an interface to the WebUSB API.
21//! You must call [`Usb::request_device()`] to ask the user for permission before
22//! any USB device can be used through this API.
23//!
24
25#![warn(missing_docs)]
26
27use std::{
28    fmt,
29    hash::{Hash, Hasher},
30    marker::PhantomData,
31    pin::Pin,
32    task::{ready, Context, Poll},
33};
34
35use futures_core::Stream;
36use futures_util::StreamExt;
37use js_sys::{Reflect, Uint8Array};
38use tokio::sync::broadcast;
39use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream};
40use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
41use wasm_bindgen_futures::{spawn_local, JsFuture};
42
43/// WebUSB error.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Error {
46    kind: ErrorKind,
47    msg: String,
48}
49
50impl Error {
51    /// Error kind.
52    pub fn kind(&self) -> ErrorKind {
53        self.kind
54    }
55
56    /// Error message.
57    pub fn msg(&self) -> &str {
58        &self.msg
59    }
60}
61
62impl fmt::Display for Error {
63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64        write!(f, "{:?}: {}", self.kind, &self.msg)
65    }
66}
67
68impl std::error::Error for Error {}
69
70/// WebUSB error kind.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72#[non_exhaustive]
73pub enum ErrorKind {
74    /// WebUSB is unsupported by this browser.
75    Unsupported,
76    /// The USB device has already been opened.
77    AlreadyOpen,
78    /// The USB device has been disconnected.
79    Disconnected,
80    /// Access denied.
81    Security,
82    /// The USB device stalled the transfer to indicate an error.
83    ///
84    /// This condition can be reset by calling [`OpenUsbDevice::clear_halt`].
85    Stall,
86    /// The USB device sent too much data.
87    Babble,
88    /// USB transfer failed.
89    Transfer,
90    /// Invalid access.
91    InvalidAccess,
92    /// Other error.
93    Other,
94}
95
96impl Error {
97    fn new(kind: ErrorKind, msg: impl AsRef<str>) -> Self {
98        Self { kind, msg: msg.as_ref().to_string() }
99    }
100}
101
102impl From<JsValue> for Error {
103    fn from(value: JsValue) -> Self {
104        if let Some(js_error) = value.dyn_ref::<js_sys::Error>() {
105            let msg = js_error.message().as_string().unwrap();
106            let kind = match js_error.name().as_string().unwrap().as_str() {
107                "NotFoundError" => ErrorKind::Disconnected,
108                "SecurityError" => ErrorKind::Security,
109                "InvalidAccessError" => ErrorKind::InvalidAccess,
110                "NetworkError" => ErrorKind::Transfer,
111                _ => ErrorKind::Other,
112            };
113            return Error::new(kind, msg);
114        }
115
116        let msg = value.as_string().unwrap_or_else(|| "unknown error".into());
117        Error::new(ErrorKind::Other, msg)
118    }
119}
120
121impl From<Error> for std::io::Error {
122    fn from(err: Error) -> Self {
123        let kind = match err.kind {
124            ErrorKind::Unsupported => std::io::ErrorKind::Unsupported,
125            ErrorKind::AlreadyOpen => std::io::ErrorKind::ResourceBusy,
126            ErrorKind::Disconnected => std::io::ErrorKind::NotConnected,
127            ErrorKind::Security => std::io::ErrorKind::PermissionDenied,
128            ErrorKind::Stall => std::io::ErrorKind::InvalidData,
129            ErrorKind::Babble => std::io::ErrorKind::UnexpectedEof,
130            ErrorKind::Transfer => std::io::ErrorKind::ConnectionReset,
131            ErrorKind::InvalidAccess => std::io::ErrorKind::InvalidInput,
132            ErrorKind::Other => std::io::ErrorKind::Other,
133        };
134        std::io::Error::new(kind, err)
135    }
136}
137
138/// WebUSB result.
139pub type Result<T> = std::result::Result<T, Error>;
140
141/// A configuration belonging to a USB device.
142#[derive(Debug, Clone)]
143#[non_exhaustive]
144pub struct UsbConfiguration {
145    /// The configuration value of this configuration.
146    ///
147    /// This is equal to the `bConfigurationValue` field of the configuration
148    /// descriptor provided by the device defining this configuration.
149    pub configuration_value: u8,
150    /// The name provided by the device to describe this configuration.
151    ///
152    /// This is equal to the value of the string descriptor with the index provided
153    /// in the `iConfiguration` field of the configuration descriptor defining
154    /// this configuration.
155    pub configuration_name: Option<String>,
156    /// The interfaces available under this configuration.
157    pub interfaces: Vec<UsbInterface>,
158}
159
160impl From<&web_sys::UsbConfiguration> for UsbConfiguration {
161    fn from(conf: &web_sys::UsbConfiguration) -> Self {
162        let iface_list = conf.interfaces();
163        let mut interfaces = Vec::new();
164        for i in 0..iface_list.length() {
165            if let Some(iface) = iface_list.get(i).dyn_ref::<web_sys::UsbInterface>() {
166                interfaces.push(UsbInterface::from(iface));
167            }
168        }
169        Self {
170            configuration_value: conf.configuration_value(),
171            configuration_name: conf.configuration_name(),
172            interfaces,
173        }
174    }
175}
176
177/// A USB interface grouping one or more alternate settings.
178#[derive(Debug, Clone)]
179#[non_exhaustive]
180pub struct UsbInterface {
181    /// The interface number.
182    pub interface_number: u8,
183    /// The currently selected alternate configuration of this interface.
184    ///
185    /// By default this is the [`UsbAlternateInterface`] from alternates with
186    /// [`UsbAlternateInterface::alternate_setting`] equal to 0.
187    /// It can be changed by calling [`OpenUsbDevice::select_alternate_interface`]
188    /// with any other value found in [`UsbInterface::alternates`]
189    pub alternate: UsbAlternateInterface,
190    /// The alternate configuration possible for this interface.
191    ///
192    /// Use [`OpenUsbDevice::select_alternate_interface`] to select an alternate
193    /// configuration.
194    pub alternates: Vec<UsbAlternateInterface>,
195    /// Returns whether or not this interface has been claimed by the current web page.
196    pub claimed: bool,
197}
198
199impl From<&web_sys::UsbInterface> for UsbInterface {
200    fn from(iface: &web_sys::UsbInterface) -> Self {
201        let alt_list = iface.alternates();
202        let mut alternates = Vec::new();
203        for i in 0..alt_list.length() {
204            if let Some(alt) = alt_list.get(i).dyn_ref::<web_sys::UsbAlternateInterface>() {
205                alternates.push(UsbAlternateInterface::from(alt));
206            }
207        }
208
209        Self {
210            interface_number: iface.interface_number(),
211            alternate: UsbAlternateInterface::from(&iface.alternate()),
212            alternates,
213            claimed: iface.claimed(),
214        }
215    }
216}
217
218/// An alternate setting containing detailed interface information.
219#[derive(Debug, Clone)]
220#[non_exhaustive]
221pub struct UsbAlternateInterface {
222    /// The alternate setting number of this interface.
223    ///
224    /// This is equal to the `bAlternateSetting` field of the interface descriptor defining this interface.
225    pub alternate_setting: u8,
226    /// The class of this interface.
227    ///
228    /// This is equal to the `bInterfaceClass` field of the interface descriptor defining this interface.
229    pub interface_class: u8,
230    /// The subclass of this interface.
231    ///
232    /// This is equal to the `bInterfaceSubClass` field of the interface descriptor defining this interface.
233    pub interface_subclass: u8,
234    /// The protocol supported by this interface.
235    ///
236    /// This is equal to the `bInterfaceProtocol` field of the interface descriptor defining this interface.
237    pub interface_protocol: u8,
238    /// The name of the interface, if one is provided by the device.
239    ///
240    /// This is the value of the string descriptor with the index specified by the `iInterface` field of
241    /// the interface descriptor defining this interface.
242    pub interface_name: Option<String>,
243    /// The endpoints belonging to this alternate setting.
244    pub endpoints: Vec<UsbEndpoint>,
245}
246
247impl From<&web_sys::UsbAlternateInterface> for UsbAlternateInterface {
248    fn from(alt: &web_sys::UsbAlternateInterface) -> Self {
249        let ep_list = alt.endpoints();
250        let mut endpoints = Vec::new();
251        for i in 0..ep_list.length() {
252            if let Some(ep) = ep_list.get(i).dyn_ref::<web_sys::UsbEndpoint>() {
253                endpoints.push(UsbEndpoint::from(ep));
254            }
255        }
256
257        Self {
258            alternate_setting: alt.alternate_setting(),
259            interface_class: alt.interface_class(),
260            interface_subclass: alt.interface_subclass(),
261            interface_protocol: alt.interface_protocol(),
262            interface_name: alt.interface_name(),
263            endpoints,
264        }
265    }
266}
267
268/// A USB endpoint provided by the USB device.
269#[derive(Debug, Clone)]
270#[non_exhaustive]
271pub struct UsbEndpoint {
272    /// The endpoint's "endpoint number" which is a value from 1 to 15 extracted from the
273    /// `bEndpointAddress` field of the endpoint descriptor defining this endpoint.
274    ///
275    /// This value is used to identify the endpoint when calling methods on [`OpenUsbDevice`].
276    pub endpoint_number: u8,
277    /// The direction in which this endpoint transfers data.
278    pub direction: UsbDirection,
279    /// The transfer type of the endpoint.
280    pub endpoint_type: UsbEndpointType,
281    /// The size of the packets that data sent through this endpoint will be divided into.
282    pub packet_size: u32,
283}
284
285impl From<&web_sys::UsbEndpoint> for UsbEndpoint {
286    fn from(ep: &web_sys::UsbEndpoint) -> Self {
287        Self {
288            endpoint_number: ep.endpoint_number(),
289            direction: ep.direction().into(),
290            endpoint_type: ep.type_().into(),
291            packet_size: ep.packet_size(),
292        }
293    }
294}
295
296/// USB endpoint type.
297#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub enum UsbEndpointType {
299    /// Provides reliable data transfer for large payloads.
300    ///
301    /// Data sent through a bulk endpoint is guaranteed to be delivered
302    /// or generate an error but may be preempted by other data traffic.
303    Bulk,
304    /// Provides reliable data transfer for small payloads.
305    ///
306    /// Data sent through an interrupt endpoint is guaranteed to be
307    /// delivered or generate an error and is also given dedicated bus time
308    /// for transmission.
309    Interrupt,
310    /// Provides unreliable data transfer for payloads that must be delivered
311    /// periodically.
312    ///
313    /// They are given dedicated bus time but if a deadline is missed the data is dropped.
314    Isochronous,
315}
316
317impl From<web_sys::UsbEndpointType> for UsbEndpointType {
318    fn from(value: web_sys::UsbEndpointType) -> Self {
319        match value {
320            web_sys::UsbEndpointType::Bulk => Self::Bulk,
321            web_sys::UsbEndpointType::Interrupt => Self::Interrupt,
322            web_sys::UsbEndpointType::Isochronous => Self::Isochronous,
323            other => unreachable!("unsupported UsbEndpointType: {other:?}"),
324        }
325    }
326}
327
328/// A USB device.
329#[derive(Clone, PartialEq, Eq)]
330pub struct UsbDevice {
331    device: web_sys::UsbDevice,
332}
333
334impl UsbDevice {
335    /// Manufacturer-provided vendor identifier.
336    pub fn vendor_id(&self) -> u16 {
337        self.device.vendor_id()
338    }
339
340    /// Manufacturer-provided product identifier.
341    pub fn product_id(&self) -> u16 {
342        self.device.product_id()
343    }
344
345    /// Device class code.
346    pub fn device_class(&self) -> u8 {
347        self.device.device_class()
348    }
349
350    /// Device subclass code.
351    pub fn device_subclass(&self) -> u8 {
352        self.device.device_subclass()
353    }
354
355    /// Device protocol code.
356    pub fn device_protocol(&self) -> u8 {
357        self.device.device_protocol()
358    }
359
360    /// Major version of the device.
361    pub fn device_version_major(&self) -> u8 {
362        self.device.device_version_major()
363    }
364
365    /// Minor version of the device.
366    pub fn device_version_minor(&self) -> u8 {
367        self.device.device_version_minor()
368    }
369
370    /// Subminor version of the device.
371    pub fn device_version_subminor(&self) -> u8 {
372        self.device.device_version_subminor()
373    }
374
375    /// Major version of USB protocol version supported by the device.
376    pub fn usb_version_major(&self) -> u8 {
377        self.device.usb_version_major()
378    }
379
380    /// Minor version of USB protocol version supported by the device.
381    pub fn usb_version_minor(&self) -> u8 {
382        self.device.usb_version_minor()
383    }
384
385    /// Subminor version of USB protocol version supported by the device.
386    pub fn usb_version_subminor(&self) -> u8 {
387        self.device.usb_version_subminor()
388    }
389
390    /// Optional manufacturer name.
391    pub fn manufacturer_name(&self) -> Option<String> {
392        self.device.manufacturer_name()
393    }
394
395    /// Optional product name.
396    pub fn product_name(&self) -> Option<String> {
397        self.device.product_name()
398    }
399
400    /// Optional serial number of the device.
401    pub fn serial_number(&self) -> Option<String> {
402        self.device.serial_number()
403    }
404
405    /// Indicates if the device is currently opened.
406    pub fn opened(&self) -> bool {
407        self.device.opened()
408    }
409
410    /// Active configuration value if any.
411    pub fn configuration(&self) -> Option<UsbConfiguration> {
412        self.device.configuration().map(|cfg| (&cfg).into())
413    }
414
415    /// All available configurations for this device.
416    pub fn configurations(&self) -> Vec<UsbConfiguration> {
417        let cfg_list = self.device.configurations();
418        let mut configurations = Vec::new();
419        for i in 0..cfg_list.length() {
420            if let Some(conf) = cfg_list.get(i).dyn_ref::<web_sys::UsbConfiguration>() {
421                configurations.push(UsbConfiguration::from(conf));
422            }
423        }
424        configurations
425    }
426
427    /// End the device session and relinquish all obtained permissions to
428    /// access the USB device.
429    pub async fn forget(self) {
430        JsFuture::from(self.device.forget()).await.unwrap();
431    }
432
433    /// Open the USB device to allow USB transfers.
434    ///
435    /// A device can only be open once.
436    pub async fn open(&self) -> Result<OpenUsbDevice> {
437        if self.opened() {
438            return Err(Error::new(ErrorKind::AlreadyOpen, "USB device is already open"));
439        }
440
441        JsFuture::from(self.device.open()).await?;
442        Ok(OpenUsbDevice { device: self.clone(), closed: false })
443    }
444}
445
446impl std::fmt::Debug for UsbDevice {
447    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
448        f.debug_struct("UsbDevice")
449            .field("vendor_id", &self.vendor_id())
450            .field("product_id", &self.product_id())
451            .field("device_class", &self.device_class())
452            .field("device_subclass", &self.device_subclass())
453            .field("device_protocol", &self.device_protocol())
454            .field("device_version_major", &self.device_version_major())
455            .field("device_version_minor", &self.device_version_minor())
456            .field("device_version_subminor", &self.device_version_subminor())
457            .field("usb_version_major", &self.usb_version_major())
458            .field("usb_version_minor", &self.usb_version_minor())
459            .field("usb_version_subminor", &self.usb_version_subminor())
460            .field("manufacturer_name", &self.manufacturer_name())
461            .field("product_name", &self.product_name())
462            .field("serial_number", &self.serial_number())
463            .field("opened", &self.opened())
464            .field("configuration", &self.configuration())
465            .field("configurations", &self.configurations())
466            .finish()
467    }
468}
469
470impl Hash for UsbDevice {
471    fn hash<H: Hasher>(&self, state: &mut H) {
472        self.vendor_id().hash(state);
473        self.product_id().hash(state);
474        self.device_class().hash(state);
475        self.device_subclass().hash(state);
476        self.device_protocol().hash(state);
477        self.device_version_major().hash(state);
478        self.device_version_minor().hash(state);
479        self.device_version_subminor().hash(state);
480        self.manufacturer_name().hash(state);
481        self.product_name().hash(state);
482        self.serial_number().hash(state);
483    }
484}
485
486impl From<web_sys::UsbDevice> for UsbDevice {
487    fn from(device: web_sys::UsbDevice) -> Self {
488        Self { device }
489    }
490}
491
492impl AsRef<web_sys::UsbDevice> for UsbDevice {
493    fn as_ref(&self) -> &web_sys::UsbDevice {
494        &self.device
495    }
496}
497
498/// USB transfer direction.
499#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
500pub enum UsbDirection {
501    /// Data is transferred from device to host.
502    In,
503    /// Data is transferred from host to device.
504    Out,
505}
506
507impl From<web_sys::UsbDirection> for UsbDirection {
508    fn from(value: web_sys::UsbDirection) -> Self {
509        match value {
510            web_sys::UsbDirection::In => Self::In,
511            web_sys::UsbDirection::Out => Self::Out,
512            other => unreachable!("unsupported UsbDirection {other:?}"),
513        }
514    }
515}
516
517impl From<UsbDirection> for web_sys::UsbDirection {
518    fn from(direction: UsbDirection) -> Self {
519        match direction {
520            UsbDirection::In => web_sys::UsbDirection::In,
521            UsbDirection::Out => web_sys::UsbDirection::Out,
522        }
523    }
524}
525
526/// A filter used to match specific USB devices by various criteria.
527///
528/// Fields left as `None` will match any value in that field.
529#[derive(Debug, Clone, Default)]
530#[non_exhaustive]
531pub struct UsbDeviceFilter {
532    /// Optional USB vendor ID.
533    pub vendor_id: Option<u16>,
534    /// Optional USB product ID.
535    pub product_id: Option<u16>,
536    /// Optional USB device class code.
537    pub class_code: Option<u8>,
538    /// Optional USB device subclass code.
539    pub subclass_code: Option<u8>,
540    /// Optional USB device protocol code.
541    pub protocol_code: Option<u8>,
542    /// Optional USB device serial number.
543    pub serial_number: Option<String>,
544}
545
546impl UsbDeviceFilter {
547    /// Creates a new, empty USB device filter.
548    pub const fn new() -> Self {
549        Self {
550            vendor_id: None,
551            product_id: None,
552            class_code: None,
553            subclass_code: None,
554            protocol_code: None,
555            serial_number: None,
556        }
557    }
558
559    /// Filter by vendor id.
560    pub const fn with_vendor_id(mut self, vendor_id: u16) -> Self {
561        self.vendor_id = Some(vendor_id);
562        self
563    }
564
565    /// Filter by product id.
566    pub const fn with_product_id(mut self, product_id: u16) -> Self {
567        self.product_id = Some(product_id);
568        self
569    }
570
571    /// Filter by device class.
572    pub const fn with_class_code(mut self, class_code: u8) -> Self {
573        self.class_code = Some(class_code);
574        self
575    }
576
577    /// Filter by device subclass.
578    pub const fn with_subclass_code(mut self, subclass_code: u8) -> Self {
579        self.subclass_code = Some(subclass_code);
580        self
581    }
582
583    /// Filter by device protocol.
584    pub const fn with_protocol_code(mut self, protocol_code: u8) -> Self {
585        self.protocol_code = Some(protocol_code);
586        self
587    }
588
589    /// Filter by serial number.
590    pub fn with_serial_number<S: Into<String>>(mut self, serial_number: S) -> Self {
591        self.serial_number = Some(serial_number.into());
592        self
593    }
594}
595
596impl From<&UsbDeviceFilter> for web_sys::UsbDeviceFilter {
597    fn from(value: &UsbDeviceFilter) -> Self {
598        let filter = web_sys::UsbDeviceFilter::new();
599        if let Some(x) = value.vendor_id {
600            filter.set_vendor_id(x);
601        }
602        if let Some(x) = value.product_id {
603            filter.set_product_id(x);
604        }
605        if let Some(x) = value.class_code {
606            filter.set_class_code(x);
607        }
608        if let Some(x) = value.subclass_code {
609            filter.set_subclass_code(x);
610        }
611        if let Some(x) = value.protocol_code {
612            filter.set_protocol_code(x);
613        }
614        if let Some(x) = &value.serial_number {
615            filter.set_serial_number(x);
616        }
617        filter
618    }
619}
620
621/// The recipient of a USB control transfer.
622#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
623pub enum UsbRecipient {
624    /// The request is intended for the USB device as a whole.
625    Device,
626    /// The request is intended for a specific interface on the USB device.
627    Interface,
628    /// The request is intended for a specific endpoint on the USB device.
629    Endpoint,
630    /// The request is intended for some other recipient.
631    Other,
632}
633
634impl From<UsbRecipient> for web_sys::UsbRecipient {
635    fn from(recipient: UsbRecipient) -> Self {
636        match recipient {
637            UsbRecipient::Device => Self::Device,
638            UsbRecipient::Interface => Self::Interface,
639            UsbRecipient::Endpoint => Self::Endpoint,
640            UsbRecipient::Other => Self::Other,
641        }
642    }
643}
644
645/// The type of USB control request.
646#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
647pub enum UsbRequestType {
648    /// A standard request defined by the USB specification.
649    Standard,
650    /// A class-specific request.
651    Class,
652    /// A vendor-specific request.
653    Vendor,
654}
655
656impl From<UsbRequestType> for web_sys::UsbRequestType {
657    fn from(req_type: UsbRequestType) -> Self {
658        match req_type {
659            UsbRequestType::Standard => Self::Standard,
660            UsbRequestType::Class => Self::Class,
661            UsbRequestType::Vendor => Self::Vendor,
662        }
663    }
664}
665
666/// USB device request options.
667#[derive(Clone, Debug)]
668struct UsbDeviceRequestOptions {
669    /// An array of filter objects for possible devices you would like to pair.
670    pub filters: Vec<UsbDeviceFilter>,
671}
672
673impl UsbDeviceRequestOptions {
674    /// Creates new USB device request options with the specified device filter.
675    pub fn new(filters: impl IntoIterator<Item = UsbDeviceFilter>) -> Self {
676        Self { filters: filters.into_iter().collect() }
677    }
678}
679
680impl From<&UsbDeviceRequestOptions> for web_sys::UsbDeviceRequestOptions {
681    fn from(value: &UsbDeviceRequestOptions) -> Self {
682        let filters = value.filters.iter().map(web_sys::UsbDeviceFilter::from).collect::<Vec<_>>();
683
684        web_sys::UsbDeviceRequestOptions::new(&filters)
685    }
686}
687
688/// USB control request.
689#[derive(Clone, Debug)]
690#[non_exhaustive]
691pub struct UsbControlRequest {
692    /// Whether the request is standard, class-specific or vendor-specific.
693    pub request_type: UsbRequestType,
694    /// The target of the transfer on the device.
695    pub recipient: UsbRecipient,
696    /// Vendor-specific command.
697    pub request: u8,
698    /// Vendor-specific request parameters.
699    pub value: u16,
700    /// The interface number of the recipient.
701    pub index: u16,
702}
703
704impl UsbControlRequest {
705    /// Creates a new USB control request with the specified
706    /// parameters.
707    pub const fn new(
708        request_type: UsbRequestType, recipient: UsbRecipient, request: u8, value: u16, index: u16,
709    ) -> Self {
710        Self { request_type, recipient, request, value, index }
711    }
712}
713
714impl From<&UsbControlRequest> for web_sys::UsbControlTransferParameters {
715    fn from(req: &UsbControlRequest) -> Self {
716        Self::new(req.index, req.recipient.into(), req.request, req.request_type.into(), req.value)
717    }
718}
719
720/// WebUSB event.
721#[derive(Debug, Clone)]
722#[non_exhaustive]
723pub enum UsbEvent {
724    /// USB device was connected.
725    Connected(UsbDevice),
726    /// USB device was disconnected.
727    Disconnected(UsbDevice),
728}
729
730/// Wrapper for making any type [Send].
731#[derive(Debug, Clone)]
732struct SendWrapper<T>(pub T);
733unsafe impl<T> Send for SendWrapper<T> {}
734
735/// WebUSB event stream.
736///
737/// Provides device change events for paired devices.
738pub struct UsbEvents {
739    // We wrap UsbEvent in SendWrapper to allow the use of
740    // BroadcastStream. However, we need to ensure that UsbEvents
741    // is !Send.
742    rx: BroadcastStream<SendWrapper<UsbEvent>>,
743    _marker: PhantomData<*const ()>,
744}
745
746impl fmt::Debug for UsbEvents {
747    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
748        f.debug_tuple("UsbEvents").finish()
749    }
750}
751
752impl Stream for UsbEvents {
753    type Item = UsbEvent;
754    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
755        loop {
756            match ready!(self.rx.poll_next_unpin(cx)) {
757                Some(Ok(event)) => break Poll::Ready(Some(event.0)),
758                Some(Err(BroadcastStreamRecvError::Lagged(_))) => (),
759                None => break Poll::Ready(None),
760            }
761        }
762    }
763}
764
765/// WebUSB device enumeration and connection.
766pub struct Usb {
767    usb: web_sys::Usb,
768    event_rx: broadcast::Receiver<SendWrapper<UsbEvent>>,
769    on_connect: Closure<dyn Fn(web_sys::UsbConnectionEvent)>,
770    on_disconnect: Closure<dyn Fn(web_sys::UsbConnectionEvent)>,
771}
772
773impl fmt::Debug for Usb {
774    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
775        f.debug_tuple("Usb").finish()
776    }
777}
778
779impl Usb {
780    /// Checks that WebUSB is available and obtains access to the WebUSB API.
781    pub fn new() -> Result<Self> {
782        let usb = Self::browser_usb()?;
783
784        let (event_tx, event_rx) = broadcast::channel(1024);
785
786        let on_connect = {
787            let event_tx = event_tx.clone();
788            Closure::wrap(Box::new(move |event: web_sys::UsbConnectionEvent| {
789                let _ = event_tx.send(SendWrapper(UsbEvent::Connected(event.device().into())));
790            }) as Box<dyn Fn(_)>)
791        };
792        usb.add_event_listener_with_callback("connect", on_connect.as_ref().unchecked_ref()).unwrap();
793
794        let on_disconnect = {
795            let event_tx = event_tx.clone();
796            Closure::wrap(Box::new(move |event: web_sys::UsbConnectionEvent| {
797                let _ = event_tx.send(SendWrapper(UsbEvent::Disconnected(event.device().into())));
798            }) as Box<dyn Fn(_)>)
799        };
800        usb.add_event_listener_with_callback("disconnect", on_disconnect.as_ref().unchecked_ref()).unwrap();
801
802        Ok(Self { usb, event_rx, on_connect, on_disconnect })
803    }
804
805    fn browser_usb() -> Result<web_sys::Usb> {
806        let global = js_sys::global();
807
808        if let Some(window) = global.dyn_ref::<web_sys::Window>() {
809            let navigator = window.navigator();
810            match Reflect::get(&navigator, &JsValue::from_str("usb")) {
811                Ok(usb) if !usb.is_null() && !usb.is_undefined() => return Ok(navigator.usb()),
812                _ => (),
813            }
814        }
815
816        if let Some(worker) = global.dyn_ref::<web_sys::WorkerGlobalScope>() {
817            let navigator = worker.navigator();
818            match Reflect::get(&navigator, &JsValue::from_str("usb")) {
819                Ok(usb) if !usb.is_null() && !usb.is_undefined() => return Ok(navigator.usb()),
820                _ => (),
821            }
822        }
823
824        Err(Error::new(ErrorKind::Unsupported, "browser does not support WebUSB"))
825    }
826
827    /// Subscribe to a stream of [`UsbEvent`]s notifying of USB device changes.
828    ///
829    /// Only events for paired devices will be provided.
830    pub fn events(&self) -> UsbEvents {
831        UsbEvents { rx: self.event_rx.resubscribe().into(), _marker: PhantomData }
832    }
833
834    /// List of paired attached devices.
835    ///
836    /// For information on pairing devices, see [`request_device`](Self::request_device).
837    pub async fn devices(&self) -> Vec<UsbDevice> {
838        let list = JsFuture::from(self.usb.get_devices()).await.unwrap();
839        js_sys::Array::from(&list)
840            .iter()
841            .map(|dev| UsbDevice::from(dev.dyn_into::<web_sys::UsbDevice>().unwrap()))
842            .collect()
843    }
844
845    /// Pairs a USB device with the specified filter criteria.
846    ///
847    /// Calling this function triggers the user agent's pairing flow.
848    pub async fn request_device(&self, filters: impl IntoIterator<Item = UsbDeviceFilter>) -> Result<UsbDevice> {
849        let opts = &UsbDeviceRequestOptions::new(filters);
850        let dev = JsFuture::from(self.usb.request_device(&opts.into())).await?;
851        Ok(dev.dyn_into::<web_sys::UsbDevice>().unwrap().into())
852    }
853}
854
855impl Drop for Usb {
856    fn drop(&mut self) {
857        self.usb
858            .remove_event_listener_with_callback("connect", self.on_connect.as_ref().unchecked_ref())
859            .unwrap();
860        self.usb
861            .remove_event_listener_with_callback("disconnect", self.on_disconnect.as_ref().unchecked_ref())
862            .unwrap();
863    }
864}
865
866/// An opened USB device.
867///
868/// Dropping this causes the USB device to be closed.
869pub struct OpenUsbDevice {
870    device: UsbDevice,
871    closed: bool,
872}
873
874impl fmt::Debug for OpenUsbDevice {
875    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
876        f.debug_struct("OpenUsbDevice").field("device", &self.device).finish()
877    }
878}
879
880impl AsRef<UsbDevice> for OpenUsbDevice {
881    fn as_ref(&self) -> &UsbDevice {
882        &self.device
883    }
884}
885
886impl OpenUsbDevice {
887    fn dev(&self) -> &web_sys::UsbDevice {
888        &self.device.device
889    }
890
891    /// The USB device.
892    pub fn device(&self) -> &UsbDevice {
893        &self.device
894    }
895
896    /// Releases all open interfaces and ends the device session.
897    ///
898    /// It is not necessary to call this method, since dropping
899    /// [OpenUsbDevice] will also close the USB device.
900    pub async fn close(mut self) -> Result<()> {
901        self.closed = true;
902        JsFuture::from(self.dev().close()).await?;
903        Ok(())
904    }
905
906    /// Resets the device and cancels all pending operations.
907    pub async fn reset(&self) -> Result<()> {
908        JsFuture::from(self.dev().reset()).await?;
909        Ok(())
910    }
911
912    /// Selects the USB device configuration with the specified index.
913    pub async fn select_configuration(&self, configuration: u8) -> Result<()> {
914        JsFuture::from(self.dev().select_configuration(configuration)).await?;
915        Ok(())
916    }
917
918    /// Claim specified interface for exclusive access.
919    pub async fn claim_interface(&self, interface: u8) -> Result<()> {
920        JsFuture::from(self.dev().claim_interface(interface)).await?;
921        Ok(())
922    }
923
924    /// Release specified interface from exclusive access.
925    pub async fn release_interface(&self, interface: u8) -> Result<()> {
926        JsFuture::from(self.dev().release_interface(interface)).await?;
927        Ok(())
928    }
929
930    /// Selects the alternate setting with the specified index for an interface.
931    pub async fn select_alternate_interface(&self, interface: u8, alternate: u8) -> Result<()> {
932        JsFuture::from(self.dev().select_alternate_interface(interface, alternate)).await?;
933        Ok(())
934    }
935
936    /// Clears a halt condition.
937    ///
938    /// A halt condition is when a data transfer to or from the device has a status of 'stall',
939    /// which requires the web page (the host system, in USB terminology) to clear that condition.
940    pub async fn clear_halt(&self, direction: UsbDirection, endpoint: u8) -> Result<()> {
941        JsFuture::from(self.dev().clear_halt(direction.into(), endpoint)).await?;
942        Ok(())
943    }
944
945    /// Check transfer status.
946    fn check_status(status: web_sys::UsbTransferStatus) -> Result<()> {
947        match status {
948            web_sys::UsbTransferStatus::Ok => Ok(()),
949            web_sys::UsbTransferStatus::Stall => Err(Error::new(ErrorKind::Stall, "USB device stalled transfer")),
950            web_sys::UsbTransferStatus::Babble => {
951                Err(Error::new(ErrorKind::Babble, "USB device sent too much data"))
952            }
953            other => unreachable!("unsupported UsbTransferStatus {other:?}"),
954        }
955    }
956
957    /// Perform a control transfer from device to host.
958    pub async fn control_transfer_in(&self, control_request: &UsbControlRequest, len: u16) -> Result<Vec<u8>> {
959        let setup = web_sys::UsbControlTransferParameters::from(control_request);
960        let res = JsFuture::from(self.dev().control_transfer_in(&setup, len)).await?;
961        let res = res.dyn_into::<web_sys::UsbInTransferResult>().unwrap();
962
963        Self::check_status(res.status())?;
964
965        let data = Uint8Array::new(&res.data().unwrap().buffer()).to_vec();
966        Ok(data)
967    }
968
969    /// Perform a control transfer from host to device.
970    pub async fn control_transfer_out(&self, control_request: &UsbControlRequest, data: &[u8]) -> Result<u32> {
971        let setup = web_sys::UsbControlTransferParameters::from(control_request);
972        let data = Uint8Array::from(data);
973        let res = JsFuture::from(self.dev().control_transfer_out_with_u8_array(&setup, &data)?).await?;
974        let res = res.dyn_into::<web_sys::UsbOutTransferResult>().unwrap();
975
976        Self::check_status(res.status())?;
977        Ok(res.bytes_written())
978    }
979
980    /// Transmits time sensitive information from the device.
981    pub async fn isochronous_transfer_in(
982        &self, endpoint: u8, packet_lens: impl IntoIterator<Item = u32>,
983    ) -> Result<Vec<Result<Vec<u8>>>> {
984        let packet_lens = packet_lens.into_iter().map(|len| js_sys::Number::from(len as f64)).collect::<Vec<_>>();
985
986        let res = JsFuture::from(self.dev().isochronous_transfer_in(endpoint, &packet_lens)).await?;
987        let res = res.dyn_into::<web_sys::UsbIsochronousInTransferResult>().unwrap();
988
989        let mut results = Vec::new();
990        for packet in res.packets() {
991            let packet = packet.dyn_into::<web_sys::UsbIsochronousInTransferPacket>().unwrap();
992            let result = match Self::check_status(packet.status()) {
993                Ok(()) => Ok(Uint8Array::new(&res.data().unwrap().buffer()).to_vec()),
994                Err(err) => Err(err),
995            };
996            results.push(result);
997        }
998
999        Ok(results)
1000    }
1001
1002    /// Transmits time sensitive information to the device.
1003    ///
1004    /// Returns the number of bytes sent of each packet.
1005    pub async fn isochronous_transfer_out(
1006        &self, endpoint: u8, packets: impl IntoIterator<Item = &[u8]>,
1007    ) -> Result<Vec<Result<u32>>> {
1008        let mut data = Vec::new();
1009        let mut lens = Vec::new();
1010
1011        for packet in packets {
1012            data.extend_from_slice(packet);
1013            lens.push(data.len());
1014        }
1015
1016        let data = Uint8Array::from(&data[..]);
1017        let lens = lens.into_iter().map(|len| js_sys::Number::from(len as f64)).collect::<Vec<_>>();
1018
1019        let res =
1020            JsFuture::from(self.dev().isochronous_transfer_out_with_u8_array(endpoint, &data, &lens)?).await?;
1021        let res = res.dyn_into::<web_sys::UsbIsochronousOutTransferResult>().unwrap();
1022
1023        let mut results = Vec::new();
1024        for packet in res.packets() {
1025            let packet = packet.dyn_into::<web_sys::UsbIsochronousOutTransferPacket>().unwrap();
1026            let result = match Self::check_status(packet.status()) {
1027                Ok(()) => Ok(packet.bytes_written()),
1028                Err(err) => Err(err),
1029            };
1030            results.push(result);
1031        }
1032
1033        Ok(results)
1034    }
1035
1036    /// Performs a bulk or interrupt transfer from specified endpoint of the device.
1037    pub async fn transfer_in(&self, endpoint: u8, len: u32) -> Result<Vec<u8>> {
1038        let res = JsFuture::from(self.dev().transfer_in(endpoint, len)).await?;
1039        let res = res.dyn_into::<web_sys::UsbInTransferResult>().unwrap();
1040
1041        Self::check_status(res.status())?;
1042
1043        let data = Uint8Array::new(&res.data().unwrap().buffer()).to_vec();
1044        Ok(data)
1045    }
1046
1047    /// Performs a bulk or interrupt transfer to the specified endpoint of the device.
1048    ///
1049    /// Returns the number of bytes sent.
1050    pub async fn transfer_out(&self, endpoint: u8, data: &[u8]) -> Result<u32> {
1051        let data = Uint8Array::from(data);
1052        let res = JsFuture::from(self.dev().transfer_out_with_u8_array(endpoint, &data)?).await?;
1053        let res = res.dyn_into::<web_sys::UsbOutTransferResult>().unwrap();
1054
1055        Self::check_status(res.status())?;
1056
1057        Ok(res.bytes_written())
1058    }
1059}
1060
1061impl Drop for OpenUsbDevice {
1062    fn drop(&mut self) {
1063        if !self.closed {
1064            let device = self.dev().clone();
1065            let fut = JsFuture::from(device.close());
1066            spawn_local(async move {
1067                let _ = fut.await;
1068            });
1069        }
1070    }
1071}