Web Serial API

repository·main·Indexed 43 days ago

https://github.com/wicg/serial

A standardized interface for web applications to interact with serial port hardware devices. It provides capabilities for requesting ports via navigator.serial.requestPort(), opening connections with specific baud rates, and reading/writing data using ReadableStream and WritableStream. The API includes support for Bluetooth serial ports via RFCOMM, allowing developers to filter by bluetoothServiceClassId and monitor logical connection states through the SerialPort.connected attribute.

Tokens
4.3K
Snippets
12
Records
19
Agent score
54%

What's inside Serial API

  1. Overview of the Serial API

    main

    The Serial API provides a way for web applications to communicate with serial ports. It allows developers to access hardware devices connected via serial interfaces directly from the browser.

    Detailed technical specifications, including motivation, privacy, and security considerations, are maintained in the project's Explainer documentation.

  2. Security and privacy constraints for Bluetooth RFCOMM

    main

    To mitigate risks related to unauthorized access to sensitive data and device fingerprinting, the Bluetooth RFCOMM implementation for Web Serial follows these security principles:

    • Permission Gating: Access to device information and communication is gated behind a user-initiated permission prompt. No device information is exposed until the user selects a specific device.
    • Service Filtering: To prevent access to sensitive standard Bluetooth profiles (like the Phone Book Access Profile or GPS/GNSS profiles), the API blocks access to all standard Bluetooth Classic service class UUIDs except for the Serial Port Profile.
    • Identifier Protection: The API does not expose the Bluetooth device address in SerialPortInfo to prevent active fingerprinting.
    • Context Restriction: Access is restricted to first-party contexts by default using Feature Policy. Third-party contexts (like iframes) must be explicitly granted access using the allow="serial" attribute.
  3. Security and Privacy considerations for Serial via Bluetooth Classic

    main

    Using Serial via Bluetooth Classic inherits all security risks associated with the standard Web Serial API. The primary security model relies on a permissions system where a user must explicitly grant access to a device, and the device must already have a bonding relationship with the host computer.

    Security Mitigations

    User agents may implement the following to mitigate risks, particularly regarding non-standard Bluetooth services:

    • Service Filtering: Blocking all Bluetooth specified services except for SPP (Serial Port Profile). Only vendor-defined service class IDs are exposed to Web Serial.
    • Enterprise Policy: Administrators may disable non-standard serial port services via policy.
    • UUID Blocklists: Browsers may block specific UUIDs to prevent hardware-level exploits.

    Privacy Considerations

    Web pages can potentially determine the class of a connected device by filtering for UUIDs or calling SerialPort.getInfo(). To protect privacy, a page cannot enumerate device attributes without first being explicitly granted access to a Bluetooth serial device that is currently bonded to the host.

  4. Understand 'logically connected' vs 'available' Bluetooth ports

    main

    The Web Serial API introduces specific states for wireless ports to handle the transient nature of Bluetooth:

    1. Available Port: A port is 'available' if it is a wired port physically connected, OR if it is a wireless port and the device is registered with the system (e.g., bonded). Serial.requestPort() and Serial.getPorts() include these even if the device is currently disconnected/out of range.
    2. Logically Connected: A wireless port is 'logically connected' if the system has an active connection to the wireless device.

    Lifecycle Note: Applications should not rely solely on onconnect and ondisconnect events for wireless ports due to potential unreliability. Instead, check SerialPort.connected before attempting to open a port, and handle errors from ReadableStreamDefaultReader.read() if the device goes out of range during communication.

  5. Security and Privacy requirements for the Serial API

    main

    The Serial API follows a security model similar to Web Bluetooth and WebUSB to mitigate risks such as malicious firmware installation or device exploitation. To use the API, your application must adhere to the following requirements:

    1. Secure Origin

    The page must be served from a secure origin (HTTPS) to prevent network-based code injection.

    2. Permission Model

    Access to devices is not automatic. You must use requestPort() to trigger a user-facing prompt. The user must actively select a specific device to grant access. This prevents 'drive-by' attacks where a site attempts to access hardware without user interaction.

    3. Cross-Origin Restrictions

    Cross-origin iframes are prohibited from using the Serial API unless the embedding page explicitly grants permission via Feature Policy.

    4. Privacy and Device Identification

    Once a user grants access to a device, the site may be able to access identifiers such as:

    • Vendor and Product IDs (for USB/Bluetooth devices).
    • Serial numbers or MAC addresses.
    • Data returned via serial commands.

    Because the user agent cannot inspect the opaque data sent over the serial line, the primary privacy defense is the permission model, which ensures a site cannot proactively enumerate available devices without user consent (similar to the File Picker API).

  6. Close a SerialPort connection

    main

    To close a port, you must first unlock the readable and writable streams.

    If you are using a continuous read loop, you should call reader.cancel() to break the loop and unlock the stream before calling port.close().

    If you are using TransformStream with pipeTo(), you must also handle the closing of the transform streams and wait for their respective closed promises to ensure the port is fully unlocked.

    // Standard close
    writer.releaseLock();
    reader.releaseLock();
    await port.close();
    
    // Close with continuous reader loop
    await reader.cancel();
    await port.close();
    
    // Close with TransformStreams
    writer.close();
    await writableStreamClosed;
    reader.cancel();
    await readableStreamClosed.catch(reason => {});
    await port.close();
  7. Handle continuous reading and stream errors

    main

    When reading data continuously, use a loop to process chunks. If a non-fatal error occurs (like a buffer overflow or parity error), the read() method will throw an exception, and port.readable will be replaced by a new ReadableStream. If a fatal error occurs (like device removal), port.readable becomes null.

    To handle both continuous reading and non-fatal errors, wrap the inner read loop in a try...catch and use an outer loop to check if port.readable is still available.

    while (port.readable) {
      const reader = port.readable.getReader();
      while (true) {
        let value, done;
        try {
          ({ value, done } = await reader.read());
        } catch (error) {
          // Handle non-fatal error (e.g., parity/framing error)
          break;
        }
        if (done) {
          break;
        }
        // Do something with |value|...
      }
      reader.releaseLock();
    }
  8. Read and write data using Streams

    main

    After opening a port, the readable and writable attributes are populated with ReadableStream and WritableStream objects. You can use TextEncoder and TextDecoder to handle ASCII/string data, or TextEncoderStream and TextDecoderStream for a more streamlined approach using pipeTo().

    Manual Encoding/Decoding

    const encoder = new TextEncoder();
    const writer = port.writable.getWriter();
    writer.write(encoder.encode("AT"));
    
    const decoder = new TextDecoder();
    const reader = port.readable.getReader();
    const { value, done } = await reader.read();
    console.log(decoder.decode(value));
    const encoder = new TextEncoderStream();
    const writableStreamClosed = encoder.readable.pipeTo(port.writable);
    const writer = encoder.writable.getWriter();
    writer.write("AT");
    
    const decoder = new TextDecoderStream();
    const readableStreamClosed = port.readable.pipeTo(decoder.writable);
    const reader = decoder.readable.getReader();
    const { value, done } = await reader.read();
    console.log(value);
    // Manual approach
    const encoder = new TextEncoder();
    const writer = port.writable.getWriter();
    writer.write(encoder.encode("AT"));
    
    const decoder = new TextDecoder();
    const reader = port.readable.getReader();
    const { value, done } = await reader.read();
    console.log(decoder.decode(value));
  9. Granting Serial API access to third-party contexts

    main

    To allow a third-party context (such as an embedded <iframe>) to use the Serial API, the first-party context must explicitly grant permission using the allow attribute. This is useful for composing functionality, such as embedding a manufacturer's 3D model viewer that needs to communicate with a printer.

    Example usage:

    <iframe src="https://manufacturer.com/printer-tool" allow="serial"></iframe>
  10. Enable Web Serial in third-party contexts (iframes)

    main

    By default, the Web Serial API is restricted to first-party contexts (['self']). If you want to allow a trusted third-party component (e.g., a manufacturer's embedded tool) to use the Serial API within your site, you must explicitly grant permission using the allow attribute on the <iframe> element.

    <iframe src="https://trusted-manufacturer.com/tool" allow="serial"></iframe>
  11. Check Serial API implementation status and availability

    main

    The Serial API is available through two primary implementations:

    1. Blink (Native Implementation): Available in Chromium-based browsers (version 89+) such as Google Chrome and Microsoft Edge.

      • Note: This is not available on Android browsers or Android WebViews because Android does not provide a direct API for serial port access.
    2. Web Serial Polyfill: A polyfill based on the WebUSB API.

      • Use Case: Use this to provide support for Android devices, as it leverages WebUSB/Web Bluetooth which are available on Android.
      • Limitations: Currently supports standard USB communications class devices, but may be expanded to support proprietary adapters or Bluetooth Low Energy UARTs via the Web Bluetooth API.
  12. Retrieve Bluetooth service UUIDs from SerialPortInfo

    main

    When calling SerialPort.getInfo(), the returned SerialPortInfo dictionary is extended to include Bluetooth-specific metadata. This allows you to identify the service used by a bonded device.

    New field:

    • bluetoothServiceClassId (string): The 128-bit Bluetooth service class UUID. For standard Serial Port Profile (SPP) devices, this will return 0x1101.