Skip to content

Socket.io Bridge example

Legacy v1 docs

v1 is on security fixes only. For new projects use the v2 WebSocket provider.

The Socket.io integration lets you access the serial port from a browser that is not physically connected to the device (e.g., a tablet or remote machine on the same network). A small Node.js server owns the USB/serial connection and proxies it over a WebSocket.

Architecture

Browser  ──WebSocket──▶  Node.js bridge server  ──serial port──▶  Device
         (socket.io)      (serialport + socket.io)

Server (Node.js)

javascript
// server.js
import { SerialPort } from "serialport";
import { Server } from "socket.io";
import { createServer } from "http";

const httpServer = createServer();
const io = new Server(httpServer, { cors: { origin: "*" } });

const ports = new Map(); // deviceUUID → SerialPort

io.on("connection", (socket) => {
  console.log("Browser connected:", socket.id);

  socket.on("connectDevice", async ({ config }) => {
    const { uuid, connectionBytes, config: portConfig, info } = config;

    const portPath = info.portName ?? (await autoDetect(portConfig));
    if (!portPath) {
      socket.emit("response", { uuid, type: "error", data: "PORT_NOT_FOUND" });
      return;
    }

    const port = new SerialPort({
      path: portPath,
      baudRate: portConfig.baudRate,
      autoOpen: false,
    });

    await new Promise((res, rej) =>
      port.open((err) => (err ? rej(err) : res())),
    );

    ports.set(uuid, port);

    // Forward incoming data back to the browser
    port.on("data", (data) => {
      socket.emit("response", {
        uuid,
        name: config.name,
        deviceNumber: config.deviceNumber,
        type: "success",
        data: Array.from(data),
      });
    });

    // Send connection bytes
    port.write(Buffer.from(connectionBytes));

    socket.emit("response", { uuid, type: "info", data: "PORT_OPENED" });
  });

  socket.on("cmd", ({ uuid, bytes }) => {
    ports.get(uuid)?.write(Buffer.from(bytes));
  });

  socket.on("disconnectDevice", ({ config }) => {
    const port = ports.get(config.uuid);
    port?.close();
    ports.delete(config.uuid);
    socket.emit("response", { uuid: config.uuid, type: "disconnect" });
  });

  socket.on("disconnectAll", () => {
    for (const [uuid, port] of ports) {
      port.close();
      ports.delete(uuid);
    }
  });

  socket.on("disconnect", () => {
    // clean up all ports this browser owned
    for (const [uuid, port] of ports) {
      port.close();
      ports.delete(uuid);
    }
  });
});

httpServer.listen(3001, () => console.log("Bridge listening on :3001"));

Install the server dependencies:

bash
npm install serialport socket.io

Browser (device class)

The device class is identical to the Arduino example with one addition — pass socket: true to the constructor:

javascript
// arduino-socket.js
import { Core, Devices } from "webserial-core";

export class Arduino extends Core {
  constructor({ no_device = 1 } = {}) {
    super({
      no_device,
      socket: true, // ← enable socket mode
    });
    this.__internal__.device.type = "arduino";
    Devices.registerType("arduino");
    Devices.add(this);
    this.getResponseAsString();

    // Tell the bridge which port to open
    this.portPath = "/dev/ttyUSB0"; // or "COM3" on Windows
  }

  serialSetConnectionConstant() {
    return this.add0x(this.parseStringToBytes("CONNECT"));
  }

  serialMessage(codex) {
    this.dispatch("serial:message", { code: codex });
  }
}

Browser (connection script)

javascript
// main.js
import { Arduino } from "./arduino-socket.js";
import { Socket } from "webserial-core";

// 1. Point to your bridge server
Socket.configure("http://localhost:3001");
Socket.prepare();

// 2. Create device — socket mode is set in the constructor
const arduino = new Arduino({ no_device: 1 });

arduino.on("serial:connected", () => console.log("Connected via socket!"));
arduino.on("serial:disconnected", () => console.log("Disconnected"));
arduino.on("serial:message", (e) => console.log(e.detail));

// 3. Connect — Core sends connectDevice over the socket automatically
arduino.connect().catch(console.error);

Custom parser

The bridge server can use two parsers (configured via device.socketPortParser):

ParserPropertyDescription
inter-byte-timeoutsocketPortParserInterval (ms)Emits after silence — good for text protocols
byte-lengthsocketPortParserLength (bytes)Emits every N bytes — good for fixed-frame protocols
javascript
// inside your constructor:
this.socketPortParser = "inter-byte-timeout";
this.socketPortParserInterval = 30; // ms

Released under the MIT License.