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