--- url: /guide/getting-started.md --- # Getting Started ## Requirements | Requirement | Version | | ----------- | ---------------------------------- | | Node.js | ≥ 18 | | TypeScript | ≥ 5.0 | | Browser | Chrome 89+ / Edge 89+ (Web Serial) | > **Note:** Web Serial requires a secure context (HTTPS or `localhost`) and > a Chromium-based browser. WebUSB and Web Bluetooth have similar requirements. ## Installation ```bash npm install webserial-core ``` ## Quick example — Web Serial ```ts import { AbstractSerialDevice, delimiter } from "webserial-core"; class MyDevice extends AbstractSerialDevice { constructor() { super({ baudRate: 9600, parser: delimiter("\n"), autoReconnect: true, }); } protected async handshake(): Promise { // Optional: send a command and wait for confirmation. // Return true to accept any port. return true; } } const device = new MyDevice(); device.on("serial:connected", () => console.log("Connected!")); device.on("serial:data", (line) => console.log("Received:", line)); device.on("serial:error", (err) => console.error("Error:", err.message)); // Opens the browser port picker and connects await device.connect(); // Send data await device.send("LED_ON\n"); // Disconnect await device.disconnect(); ``` ## Switching transport providers All four adapters implement the same `SerialProvider` interface. Inject your chosen provider once before constructing any device: ```ts import { AbstractSerialDevice } from "webserial-core"; import { WebUsbProvider } from "webserial-core/adapters/web-usb"; // or: import { createBluetoothProvider } from "webserial-core/adapters/web-bluetooth"; // or: import { createWebSocketProvider } from "webserial-core/adapters/websocket"; // WebUSB polyfill (useful on Android Chrome) AbstractSerialDevice.setProvider(new WebUsbProvider()); // Web Bluetooth NUS AbstractSerialDevice.setProvider(createBluetoothProvider()); // WebSocket bridge (Node.js server required — see demos/websocket/README.md) AbstractSerialDevice.setProvider( createWebSocketProvider("ws://127.0.0.1:8080/?token=TOKEN"), ); ``` After calling `setProvider`, every `AbstractSerialDevice` subclass instance will use that transport automatically. ## Configuration options All options are passed to the `super()` call in your device constructor: ```ts super({ baudRate: 9600, // Serial baud rate dataBits: 8, // 7 or 8 stopBits: 1, // 1 or 2 parity: "none", // "none" | "even" | "odd" flowControl: "none", // "none" | "hardware" bufferSize: 255, // Internal read buffer size (bytes) parser: delimiter("\n"), // How to split incoming bytes into messages commandTimeout: 3000, // ms before a queued command times out autoReconnect: true, // Automatically reconnect on disconnect autoReconnectInterval: 1500, // ms between reconnect attempts handshakeTimeout: 2000, // ms allowed for handshake() to complete filters: [], // USB vendor/product ID filters }); ``` ## Next steps * Read the [Architecture guide](./architecture.md) to understand how the pieces fit together. * Browse the [API reference](/api/abstract-serial-device) for full method and event documentation. * Check the [examples](/examples/web-serial) for copy-paste demos. * If you are upgrading from v1, see the [Migration guide](./migration-v1-v2.md). --- --- url: /guide/architecture.md --- # Architecture ## Overview ``` ┌──────────────────────────────────────────────────────────┐ │ Your device class │ │ (extends AbstractSerialDevice) │ └───────────────────────┬──────────────────────────────────┘ │ inherits ▼ ┌──────────────────────────────────────────────────────────┐ │ AbstractSerialDevice │ │ ┌─────────────────┐ ┌──────────┐ ┌────────────────┐ │ │ │ SerialRegistry │ │ CmdQueue │ │ SerialEventEmit│ │ │ │ (port locks) │ │ (FIFO) │ │ ter │ │ │ └─────────────────┘ └──────────┘ └────────────────┘ │ │ │ │ connect() / disconnect() / send() / handshake() │ └───────────────────────┬──────────────────────────────────┘ │ delegates to ▼ ┌──────────────────────────────────────────────────────────┐ │ SerialProvider │ │ (requestPort, getPorts — returns SerialPort objects) │ ├──────────────────────────────────────────────────────────┤ │ ┌────────────────┐ ┌──────────────────┐ ┌───────────┐ │ │ │ Navigator │ │ WebUsbProvider │ │ WebBT/WS │ │ │ │ (native Web │ │ (USB polyfill) │ │ providers │ │ │ │ Serial API) │ │ │ │ │ │ │ └────────────────┘ └──────────────────┘ └───────────┘ │ └──────────────────────────────────────────────────────────┘ ``` ## Core components ### `AbstractSerialDevice` The base class your device inherits from. It manages the full connection lifecycle: 1. **`connect()`** — calls `provider.requestPort()`, opens the port, runs `handshake()`, then starts the read loop. 2. **Read loop** — continuously reads `Uint8Array` chunks from the port's `ReadableStream` and feeds them through the configured `SerialParser`. Every time the parser emits a value, a `serial:data` event fires. 3. **`send(data)`** — encodes strings/bytes, enqueues the write in `CommandQueue`, and flushes to the port's `WritableStream`. 4. **`disconnect()`** — cancels the read loop, drains the queue, closes the port, and fires `serial:disconnected`. 5. **Auto-reconnect** — if `autoReconnect: true` and the connection drops, the device waits `autoReconnectInterval` ms and calls `connect()` again. ### `SerialProvider` ```ts interface SerialProvider { requestPort(options?: { filters?: SerialPortFilter[] }): Promise; getPorts(): Promise; } ``` The default provider uses `navigator.serial` (native Web Serial API). Inject a custom provider for WebUSB, Web Bluetooth, or WebSocket. ### `SerialParser` ```ts interface SerialParser { transform(chunk: Uint8Array, push: (value: T) => void): void; flush?(push: (value: T) => void): void; } ``` Receives raw bytes and calls `push()` for each complete message. Three built-in parsers are included: | Parser | Factory | Description | | ------------ | ---------------- | ------------------------------------------- | | Delimiter | `delimiter(sep)` | Splits on a byte sequence (e.g. `"\n"`) | | Fixed length | `fixedLength(n)` | Emits every N bytes | | Raw | `raw()` | Passes each chunk unchanged as `Uint8Array` | ### `CommandQueue` A FIFO queue that serialises writes to the port. Each `send()` call enqueues a write. If a command does not complete within `commandTimeout` ms, a `serial:timeout` event fires and the next command proceeds. ### `SerialRegistry` A global singleton that tracks which ports are currently open. Prevents two `AbstractSerialDevice` instances from opening the same port simultaneously. ### `SerialEventEmitter` A minimal typed event emitter. `on`, `off`, and `emit` are all fully typed using the `SerialEventMap` generic, so TypeScript knows the exact payload type for every event. ## Data flow ``` SerialPort.readable (ReadableStream) │ ▼ Read loop (pump) │ Uint8Array chunks ▼ SerialParser.transform(chunk, push) │ Parsed values of type T ▼ emit("serial:data", value) │ ▼ Your event handler ``` ## Provider injection ```ts // Global — affects all AbstractSerialDevice instances AbstractSerialDevice.setProvider(myProvider); // Or pass it directly to the constructor (per-instance) super({ ...options, provider: myProvider }); ``` --- --- url: /guide/migration-v1-v2.md --- # Migrating from v1 > **v2 is a complete rewrite of webserial-core and is not backwards compatible with v1.** Version 2 introduces a new architecture, new adapter system, new folder structure, new build output format, and changes to every public API. There is no incremental upgrade path. *** ## Recommended approach The cleanest way to migrate is to **start fresh** with v2: 1. Uninstall v1 and install v2: ```bash npm remove webserial-core npm install webserial-core@latest ``` 2. Delete any existing extension of the old `SerialDevice` class. 3. Create a new class extending `AbstractSerialDevice` following the [Getting started](./getting-started.md) guide. *** ## Why a full rewrite? Version 1 was a prototype that proved the concept. Version 2 was designed from scratch with the following goals that required breaking changes at every layer: * **Strict TypeScript** — no `any`, full JSDoc, named interfaces for every option * **Modular adapter system** — WebUSB, Web Bluetooth, and WebSocket are first-class transports * **Dual ESM + CJS output** — proper `.mjs` / `.cjs` / `.umd.cjs` build via Vite * **Isolated responsibilities** — parsers, queue, events, registry, and adapters are all separate modules that can be tested independently * **English-only source** — all source code, JSDoc, and error messages are in English *** ## Reference :::tip Start here Read the [Getting started](./getting-started.md) guide for the full v2 API. ::: * [API — AbstractSerialDevice](../api/abstract-serial-device.md) * [API — Events](../api/events.md) * [API — Parsers](../api/parsers.md) * [Examples](../examples/web-serial.md) --- --- url: /guide/contributing.md --- # Contributing Thank you for your interest in contributing to **webserial-core**! ## Development setup ```bash git clone https://github.com/danidoble/webserial-core.git cd webserial-core npm install ``` Start the Vite dev server to test the demos interactively: ```bash npm run dev ``` Then open any of the demo pages in your browser: * `http://localhost:5173/` — Web Serial (native) * `http://localhost:5173/demos/web-usb/` — WebUSB Polyfill * `http://localhost:5173/demos/web-bluetooth/` — Web Bluetooth * `http://localhost:5173/demos/websocket/` — WebSocket Bridge ## Code style * **TypeScript strict mode** — no `any`, all public APIs fully typed. * **ESM imports** — use `.js` extension on relative imports (e.g. `import x from "./foo.js"`). Vite resolves these to the `.ts` source files. * **English only** — all comments, JSDoc, variable names, and string literals must be in English. * **JSDoc** — all exported symbols must have JSDoc with `@param`, `@returns`, and `@throws` where applicable. Run lint and format checks before pushing: ```bash npm run lint npm run format ``` ## Building ```bash npm run build ``` Produces: ``` dist/ webserial-core.mjs ← ESM (tree-shakeable, for bundlers) webserial-core.cjs ← CommonJS (Node.js / legacy bundlers) webserial-core.umd.cjs ← UMD (standalone


    
  

```

## Response format

By default v1 returns device data as **`Uint8Array`**. Call one of the helpers inside your constructor to change the format:

| Method                            | Returns                  |
| --------------------------------- | ------------------------ |
| `this.getResponseAsUint8Array()`  | `Uint8Array` *(default)* |
| `this.getResponseAsString()`      | decoded `string`         |
| `this.getResponseAsArrayHex()`    | `string[]` hex values    |
| `this.getResponseAsArrayBuffer()` | `ArrayBuffer`            |

## Differences from v2

| Feature                 | v1                        | v2                                       |
| ----------------------- | ------------------------- | ---------------------------------------- |
| Transports              | Web Serial only           | Web Serial, WebUSB, Bluetooth, WebSocket |
| TypeScript generics     | Partial                   | Full                                     |
| Parsers                 | Manual in `serialMessage` | Built-in (delimiter, fixed-length, raw)  |
| Provider injection      | No                        | Yes                                      |
| Auto-reconnect back-off | No                        | Yes                                      |
| Command queue           | ❌                        | ✅                                       |
| Auto-reconnect          | ❌                        | ✅                                       |
| Provider injection      | ❌                        | ✅                                       |
| Tree-shakeable          | ❌                        | ✅                                       |

## Migrating to v2

See the [Migration Guide](/guide/migration-v1-v2) for a step-by-step upgrade path.

---

---
url: /v1/api/core.md
---

# Core

::: warning Legacy v1 docs
v1 is on security fixes only. See [v2 docs](/api/abstract-serial-device) for the current release.
:::

`Core` is the base class for all v1 devices. You always **subclass** it — never instantiate it directly.

```typescript
import { Core, Devices } from "webserial-core";

class MyDevice extends Core {
  constructor() {
    super({ baudRate: 9600 });
    this.__internal__.device.type = "my-device";
    Devices.registerType("my-device");
    Devices.add(this);
    this.getResponseAsString();
  }

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

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

## Constructor

```typescript
new Core(options?: CoreConstructorParams)
```

| Parameter                     | Type                         | Default                                                                                                | Description                                                 |
| ----------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| `filters`                     | `SerialPortFilter[] \| null` | `null`                                                                                                 | Filters passed to `navigator.serial.requestPort()`          |
| `config_port`                 | `SerialOptions`              | `{ baudRate: 9600, dataBits: 8, stopBits: 1, parity: "none", bufferSize: 32768, flowControl: "none" }` | Serial port configuration                                   |
| `no_device`                   | `number`                     | `1`                                                                                                    | Device instance number (used by the registry)               |
| `device_listen_on_channel`    | `number \| string`           | `1`                                                                                                    | Channel/port number passed to `serialSetConnectionConstant` |
| `bypassSerialBytesConnection` | `boolean`                    | `false`                                                                                                | Skip automatic connection-byte sending                      |
| `socket`                      | `boolean`                    | `false`                                                                                                | Use Socket.io bridge instead of direct Web Serial           |
| `transformStream`             | `false \| TransformStream`   | `false`                                                                                                | Custom transform stream to inject into the read pipeline    |

## Methods to implement in subclass

### `serialSetConnectionConstant(listenOnPort?)`

Called automatically during connection. Must return the bytes to send for device handshake.

```typescript
serialSetConnectionConstant(listenOnPort = 1): string | Uint8Array | string[] | number[] | null
```

**Example**

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

***

### `serialMessage(code)`

Called for every message received from the device. Parse and dispatch events here.

```typescript
serialMessage(code: string | Uint8Array | string[] | ArrayBuffer): void
```

**Example**

```javascript
serialMessage(codex) {
  if (codex === "connected") {
    this.dispatch("serial:message", { name: "connected", no_code: 100 });
  }
}
```

***

### `serialCorruptMessage(code)` *(optional)*

Called when a received message fails validation. Override to handle corrupt responses.

```typescript
serialCorruptMessage(code: Uint8Array | string[] | string | null): void
```

## Connection methods

### `connect()`

Opens the system port picker (if no port is already saved) and establishes the serial connection.

```typescript
connect(): Promise
```

Returns `true` when connected.

***

### `disconnect(detail?)`

Closes the serial port and emits `serial:disconnected`.

```typescript
disconnect(detail?: object | null): Promise
```

***

### `serialConnect()`

Low-level connect (no port picker). Reopens a previously granted port.

```typescript
serialConnect(): Promise
```

***

### `serialDisconnect()`

Low-level close of the serial port streams.

```typescript
serialDisconnect(): Promise
```

***

### `serialForget()`

Calls `port.forget()` to revoke the browser permission for the saved port.

```typescript
serialForget(): Promise
```

## Queue methods

### `appendToQueue(arr, action)`

Enqueues bytes to be written to the device. Sends `internal:queue` to process the next item.

```typescript
appendToQueue(
  arr: string | Uint8Array | string[] | number[],
  action: string
): Promise
```

***

### `sendConnect()`

Sends the connection-constant bytes through the queue.

```typescript
sendConnect(): Promise
```

***

### `sendCustomCode({ code })`

Enqueues arbitrary bytes with action `"custom"`.

```typescript
sendCustomCode({ code }: { code: string | Uint8Array | string[] | number[] }): Promise
```

***

### `clearSerialQueue()`

Empties the command queue immediately.

```typescript
clearSerialQueue(): void
```

***

### `softReload()`

Clears the last error state and emits `serial:soft-reload`.

```typescript
softReload(): void
```

## Response format helpers

Call exactly **one** of these in your constructor:

```javascript
this.getResponseAsUint8Array(); // default — Uint8Array
this.getResponseAsString(); // decoded string (TextDecoder)
this.getResponseAsArrayHex(); // string[] of hex values
this.getResponseAsArrayBuffer(); // ArrayBuffer
```

## Properties

### State

| Property         | Type             | Description                                          |
| ---------------- | ---------------- | ---------------------------------------------------- |
| `isConnected`    | `boolean`        | `true` when the port is open and readable/writable   |
| `isConnecting`   | `boolean`        | `true` while the connection handshake is in progress |
| `isDisconnected` | `boolean`        | `true` when not connected                            |
| `lastAction`     | `string \| null` | The `action` string of the last queued command       |

### Identity

| Property       | Type     | Description                                            |
| -------------- | -------- | ------------------------------------------------------ |
| `uuid`         | `string` | Auto-generated `crypto.randomUUID()`                   |
| `typeDevice`   | `string` | Device type string (e.g. `"arduino"`)                  |
| `deviceNumber` | `number` | Instance number set via `no_device` in the constructor |

### Queue

| Property | Type          | Description                             |
| -------- | ------------- | --------------------------------------- |
| `queue`  | `QueueData[]` | Current pending items in the send queue |

### Port configuration

| Property           | Type                       | Description                                |
| ------------------ | -------------------------- | ------------------------------------------ |
| `serialFilters`    | `SerialPortFilter[]`       | Get/set port filters (throws if connected) |
| `serialConfigPort` | `SerialOptions`            | Get/set port options (throws if connected) |
| `portPath`         | `string \| null`           | COM/tty path hint for socket mode          |
| `portVendorId`     | `string \| number \| null` | USB vendor ID hint for socket mode         |
| `portProductId`    | `string \| number \| null` | USB product ID hint for socket mode        |
| `useRTSCTS`        | `boolean`                  | Enable RTS/CTS hardware flow control       |

### Response parsing

| Property                      | Type                       | Description                                                                |
| ----------------------------- | -------------------------- | -------------------------------------------------------------------------- |
| `fixedBytesMessage`           | `number \| null`           | Expect exactly N bytes per message; `null` = variable                      |
| `timeoutBeforeResponseBytes`  | `number`                   | Inter-byte silence (ms) before declaring a response complete. Default `50` |
| `responseDelimited`           | `boolean`                  | Use `responseLimiter` to split messages                                    |
| `responseLimiter`             | `string \| RegExp \| null` | Delimiter for message boundaries                                           |
| `responsePrefixLimited`       | `boolean`                  | Limiter appears at the **start** of each message                           |
| `responseSufixLimited`        | `boolean`                  | Limiter appears at the **end** of each message (default `true`)            |
| `bypassSerialBytesConnection` | `boolean`                  | Skip connection-byte dispatch                                              |
| `connectionBytes`             | `Uint8Array`               | Read-only computed connection bytes                                        |

### Socket mode

| Property                   | Type                                    | Description                                  |
| -------------------------- | --------------------------------------- | -------------------------------------------- |
| `useSocket`                | `boolean`                               | Whether socket mode is active                |
| `socketPortParser`         | `"byte-length" \| "inter-byte-timeout"` | Parser type for the socket bridge            |
| `socketPortParserInterval` | `number`                                | Inter-byte timeout (ms) for socket parser    |
| `socketPortParserLength`   | `number`                                | Fixed byte length for socket parser          |
| `configDeviceSocket`       | `object`                                | Full config object sent to the socket bridge |

## Byte-conversion utilities

```typescript
parseStringToBytes(string: string, end?: string): string[]
parseStringToTextEncoder(string: string, end?: string): Uint8Array
parseUint8ToHex(array: Uint8Array): string[]
parseHexToUint8(array: string[]): Uint8Array
stringArrayToUint8Array(strings: string[]): Uint8Array
parseUint8ArrayToString(array: Uint8Array | string[]): string
stringToArrayHex(string: string): string[]
stringToArrayBuffer(string: string, end?: string): ArrayBufferLike
hexToAscii(hex: string | number): string
asciiToHex(asciiString: string): string
decToHex(dec: number | string): string
hexToDec(hex: string): number
hexMaker(val?: string, min?: number): string
add0x(bytes: string[]): string[]
bytesToHex(bytes: string[]): string[]
sumHex(arr: string[]): string
validateBytes(data: string | Uint8Array | string[] | number[]): Uint8Array
```

---

---
url: /v1/api/devices.md
---

# Devices

::: warning Legacy v1 docs
v1 is on security fixes only. See [v2 docs](/api/abstract-serial-device) for the current release.
:::

`Devices` is a **static registry** that keeps track of every device instance in your app. It is a `Dispatcher` subclass so you can listen to the `change` event globally.

```typescript
import { Devices } from "webserial-core";
```

## Static methods

### `registerType(type)`

Creates a slot in the registry for a new device type. Call this once inside your subclass constructor before calling `Devices.add()`.

```typescript
Devices.registerType(type: string): void
```

```javascript
Devices.registerType("arduino");
```

***

### `add(device)`

Registers a device instance. Typically called at the end of your subclass constructor.

```typescript
Devices.add(device: Core): number  // returns the index in the type bucket
```

Throws if a device with the same UUID is already registered.

***

### `get(type, id)`

Returns a device by type name and UUID.

```typescript
Devices.get(type: string, id: string): Core
```

***

### `getByNumber(type, deviceNumber)`

Returns the device with the given `deviceNumber` within a type bucket, or `null`.

```typescript
Devices.getByNumber(type: string, deviceNumber: number): Core | null
```

***

### `getCustom(type, deviceNumber?)`

Alias of `getByNumber`.

```typescript
Devices.getCustom(type: string, deviceNumber?: number): Core | null
```

***

### `getAll(type?)`

Returns all devices of a given type (or all devices grouped by type when `type` is `null`).

```typescript
Devices.getAll(type?: string | null): IDevice | IDevices
```

***

### `getList()`

Returns a flat array of every registered device regardless of type.

```typescript
Devices.getList(): Core[]
```

***

### `connectToAll()`

Calls `connect()` on every device that is currently disconnected.

```typescript
Devices.connectToAll(): Promise
```

Returns `true` when every device is connected.

***

### `disconnectAll()`

Calls `disconnect()` on every device that is currently connected.

```typescript
Devices.disconnectAll(): Promise
```

***

### `areAllConnected()`

Returns `true` when every registered device is connected.

```typescript
Devices.areAllConnected(): Promise
```

***

### `areAllDisconnected()`

Returns `true` when every registered device is disconnected.

```typescript
Devices.areAllDisconnected(): Promise
```

***

### `getAllConnected()`

Returns an array of all devices that are currently connected.

```typescript
Devices.getAllConnected(): Promise
```

***

### `getAllDisconnected()`

Returns an array of all devices that are currently disconnected.

```typescript
Devices.getAllDisconnected(): Promise
```

## Instance event

The singleton `Devices.instance` emits one event:

| Event    | Payload                   | Description                                                      |
| -------- | ------------------------- | ---------------------------------------------------------------- |
| `change` | `{ devices, dispatcher }` | Fired whenever a device is added or its connection state changes |

```javascript
Devices.instance.on("change", (event) => {
  const { devices, dispatcher } = event.detail;
  console.log("Registry changed:", Object.keys(devices));
});
```

## Usage example

```javascript
import { Arduino } from "./arduino.js";
import { Devices } from "webserial-core";

const a1 = new Arduino({ no_device: 1 });
const a2 = new Arduino({ no_device: 2 });

// Connect both at once
await Devices.connectToAll();

// Retrieve by number
const first = Devices.getByNumber("arduino", 1);
console.log(first.isConnected); // true

// Disconnect all
await Devices.disconnectAll();
```

---

---
url: /v1/api/dispatcher.md
---

# Dispatcher

::: warning Legacy v1 docs
v1 is on security fixes only. See [v2 docs](/api/abstract-serial-device) for the current release.
:::

`Dispatcher` extends the native `EventTarget` and adds helper methods for the event-driven communication system. Both `Core` and `Devices` extend this class.

```typescript
import { Dispatcher } from "webserial-core";
```

## Methods

### `on(type, callback)`

Registers an event listener and marks the event type as active.

```typescript
on(type: string, callback: EventListenerOrEventListenerObject): void
```

```javascript
device.on("serial:connected", (event) => {
  console.log("connected", event.detail);
});
```

***

### `off(type, callback)`

Removes a previously registered event listener.

```typescript
off(type: string, callback: EventListenerOrEventListenerObject): void
```

```javascript
const handler = (e) => console.log(e.detail);
device.on("serial:message", handler);
// later...
device.off("serial:message", handler);
```

***

### `dispatch(type, data?)`

Synchronously dispatches a `SerialEvent` (a `CustomEvent` subclass). The `data` value becomes `event.detail`.

```typescript
dispatch(type: string, data?: string | number | boolean | object | null): void
```

***

### `dispatchAsync(type, data?, ms?)`

Dispatches an event after `ms` milliseconds (default `100`).

```typescript
dispatchAsync(type: string, data?: any, ms?: number): void
```

***

### `serialRegisterAvailableListener(type)`

Registers a new event type so it appears in `availableListeners`. Called internally in the `Core` constructor — use it in your subclass to declare custom events.

```typescript
serialRegisterAvailableListener(type: string): void
```

```javascript
// inside your subclass constructor:
this.serialRegisterAvailableListener("my-custom-event");
```

***

### `removeAllListeners()`

Removes every listener except `internal:queue` and resets all listening flags to `false`. Useful for cleanup.

```typescript
removeAllListeners(): void
```

## Properties

### `availableListeners`

Read-only array of `{ type: string; listening: boolean }` objects representing all registered event types.

```typescript
get availableListeners(): { type: string; listening: boolean }[]
```

```javascript
console.table(device.availableListeners);
// [
//   { type: "serial:connected",    listening: true },
//   { type: "serial:disconnected", listening: false },
//   ...
// ]
```

---

---
url: /v1/api/socket.md
---

# Socket

::: warning Legacy v1 docs
v1 is on security fixes only. See [v2 docs](/api/websocket-provider) for the current release.
:::

`Socket` is a singleton that manages the Socket.io connection to a serial-bridge server. It forwards commands from the browser to a Node.js server that owns the physical serial port.

```typescript
import { Socket } from "webserial-core";
```

> **Peer dependency** — the Socket integration requires `socket.io-client` to be installed:
>
> ```bash
> npm install socket.io-client
> ```

## Server setup

You need a companion Node.js server. A minimal example is included in the `tests/ws-serial-bridge/` directory of the repository. It listens for `connectDevice`, `disconnectDevice`, `disconnectAll`, and `cmd` events and proxies them through the `serialport` npm package.

## Configuration

### `Socket.configure(uri?, options?)`

Sets the URI and Socket.io options **before** calling `prepare()`. Throws if `prepare()` has already been called.

```typescript
configure(uri?: string, options?: Partial): void
```

```javascript
Socket.configure("http://192.168.1.10:3001", { transports: ["websocket"] });
```

Default URI: `http://localhost:3001`.

***

### `Socket.prepare()`

Creates the `socket.io-client` instance and registers all internal event handlers. Safe to call multiple times — subsequent calls are no-ops if already connected.

```typescript
prepare(): void
```

***

### `Socket.disconnect()`

Closes the socket and cleans up all listeners.

```typescript
disconnect(): void
```

## Device interaction

### `Socket.connectDevice(config)`

Emits `connectDevice` to the server with the device configuration object.

```typescript
connectDevice(config: object): void
```

The `config` object matches `device.configDeviceSocket`.

***

### `Socket.disconnectDevice(config)`

Emits `disconnectDevice` to the server.

```typescript
disconnectDevice(config: object): void
```

***

### `Socket.disconnectAllDevices()`

Emits `disconnectAll` to the server — useful for cleanup on page unload.

```typescript
disconnectAllDevices(): void
```

***

### `Socket.write(data)`

Emits `cmd` to the server, which forwards the bytes to the serial port.

```typescript
write(data: object): void
```

## State

```typescript
Socket.isConnected(): boolean
Socket.isDisconnected(): boolean
Socket.socketId: string | null  // Socket.io socket ID
Socket.uri: string              // current server URI
Socket.options: object          // current Socket.io options
```

## Window events

The singleton dispatches these events on `window` so that `Core` instances can react to socket state changes:

| Event                        | Description                             |
| ---------------------------- | --------------------------------------- |
| `serial:socket:connected`    | Socket successfully connected           |
| `serial:socket:disconnected` | Socket disconnected or connection error |

## Usage example

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

// 1. Configure before any device uses it
Socket.configure("http://localhost:3001");
Socket.prepare();

// 2. Create device in socket mode
const arduino = new Arduino({ socket: true, no_device: 1 });

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

// 3. Connect — Core handles the connectDevice emit internally
arduino.connect().catch(console.error);
```

---

---
url: /v1/api/serial-error.md
---

# SerialError

::: warning Legacy v1 docs
v1 is on security fixes only. See [v2 docs](/api/abstract-serial-device) for the current release.
:::

`SerialError` is a structured error class that wraps serial communication failures with a typed code and optional context.

```typescript
import { SerialError, SerialErrorCode } from "webserial-core";
```

## Constructor

```typescript
new SerialError(
  message: string,
  code?: SerialErrorCode,
  context?: Record
)
```

| Parameter | Default                         | Description                                              |
| --------- | ------------------------------- | -------------------------------------------------------- |
| `message` | —                               | Human-readable description                               |
| `code`    | `SerialErrorCode.UNKNOWN_ERROR` | Error category from `SerialErrorCode`                    |
| `context` | `undefined`                     | Extra key/value information (port name, baud rate, etc.) |

## `SerialErrorCode` enum

| Value                   | Description                                   |
| ----------------------- | --------------------------------------------- |
| `CONNECTION_FAILED`     | Could not open the serial port                |
| `DISCONNECTION_FAILED`  | Port failed to close cleanly                  |
| `WRITE_FAILED`          | Writing bytes to the port failed              |
| `READ_FAILED`           | Reading bytes from the port failed            |
| `TIMEOUT`               | No response received within the allotted time |
| `PORT_NOT_FOUND`        | Serial port no longer found / unplugged       |
| `PERMISSION_DENIED`     | Browser denied access to the port             |
| `DEVICE_NOT_SUPPORTED`  | Device type is not supported                  |
| `INVALID_CONFIGURATION` | Bad constructor options                       |
| `SOCKET_ERROR`          | Socket.io communication error                 |
| `UNKNOWN_ERROR`         | General uncategorised error                   |

## Properties

| Property    | Type                                   | Description                |
| ----------- | -------------------------------------- | -------------------------- |
| `name`      | `string`                               | Always `"SerialError"`     |
| `message`   | `string`                               | Human-readable message     |
| `code`      | `SerialErrorCode`                      | Error category             |
| `context`   | `Record \| undefined` | Optional extra context     |
| `timestamp` | `Date`                                 | When the error was created |
| `stack`     | `string \| undefined`                  | V8 stack trace             |

## Methods

### `toJSON()`

Returns a plain object suitable for logging or serialisation.

```typescript
toJSON(): Record
```

```javascript
const err = new SerialError(
  "Failed to connect",
  SerialErrorCode.CONNECTION_FAILED,
  { baudRate: 9600 },
);

console.log(err.toJSON());
// {
//   name: "SerialError",
//   message: "Failed to connect",
//   code: "CONNECTION_FAILED",
//   context: { baudRate: 9600 },
//   timestamp: "2024-01-15T10:30:00.000Z",
//   stack: "..."
// }
```

### `toString()`

Returns a formatted single-line string.

```typescript
toString(): string
// "SerialError [CONNECTION_FAILED]: Failed to connect | Context: {"baudRate":9600}"
```

## Usage

```javascript
arduino.on("serial:error", (event) => {
  const err = event.detail;
  if (err instanceof SerialError) {
    console.error(`[${err.code}] ${err.message}`, err.context);
  } else {
    console.error(err);
  }
});
```

---

---
url: /v1/api/events.md
---

# Events

::: warning Legacy v1 docs
v1 is on security fixes only. See [v2 docs](/api/events) for the current release.
:::

v1 uses a custom `CustomEvent`-based system. Listen with `device.on(eventName, handler)`. The handler receives the native `CustomEvent`; payload is always in `event.detail`.

```javascript
arduino.on("serial:connected", (event) => {
  console.log("connected!", event.detail);
});
```

## Device events

| Event                    | Detail                             | Description                                                             |
| ------------------------ | ---------------------------------- | ----------------------------------------------------------------------- |
| `serial:connecting`      | `null`                             | Emitted when the connection process starts                              |
| `serial:connected`       | `null`                             | Emitted when the device is fully connected and ready                    |
| `serial:disconnected`    | `object \| null`                   | Emitted when the device disconnects (detail may carry an error message) |
| `serial:need-permission` | `null`                             | User must interact with the browser to grant port access                |
| `serial:unsupported`     | `null`                             | Web Serial API is not available in this browser                         |
| `serial:reconnect`       | `{}`                               | Emitted on a connection timeout when still in `connect` phase           |
| `serial:message`         | device-specific                    | Emitted from your `serialMessage()` implementation                      |
| `serial:corrupt-message` | `{ code }`                         | Emitted from your `serialCorruptMessage()` implementation               |
| `serial:sent`            | `{ action, bytes }`                | Emitted after bytes are written to the port                             |
| `serial:timeout`         | `{ message, action, code, bytes }` | Emitted when a queued command does not receive a response in time       |
| `serial:soft-reload`     | `{}`                               | Emitted by `softReload()`; use it to reset your local state             |
| `serial:error`           | `Error \| DOMException`            | Any uncaught error from connection or I/O                               |
| `serial:lost`            | `null`                             | Emitted when the port is unexpectedly removed/closed                    |
| `debug`                  | `{ type, data }`                   | Internal debug event (only fired when debug mode is active)             |

## Registry event

| Event    | Detail                    | Description                                                |
| -------- | ------------------------- | ---------------------------------------------------------- |
| `change` | `{ devices, dispatcher }` | Fired on `Devices.instance` whenever context state changes |

## Socket events (window-level)

When `socket: true` is set in the constructor, these events are dispatched on `window`:

| Event                        | Description                      |
| ---------------------------- | -------------------------------- |
| `serial:socket:connected`    | Socket.io connection established |
| `serial:socket:disconnected` | Socket.io connection lost        |

## Event detail shapes

### `serial:timeout`

```typescript
{
  message: string;   // "Operation response timed out."
  action: string;    // the queue action (e.g. "connect", "hi")
  code: Uint8Array | string[] | null;
  bytes: Uint8Array | string[];
  no_code: number;
}
```

### `serial:sent`

```typescript
{
  action: string; // the queue action
  bytes: Uint8Array; // the bytes that were written
}
```

### `serial:disconnected`

```typescript
{
  error?: string;    // reason, e.g. "Port is closed, not readable or writable."
}
```

---

---
url: /v1/examples/arduino.md
---

# Arduino example

::: warning Legacy v1 docs
v1 is on security fixes only. For new projects use [v2](/examples/web-serial).
:::

This is a complete working example of connecting to an Arduino via v1 of webserial-core.

## Arduino firmware

Upload the following sketch to your board:

```cpp
// firmware.ino

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    String comando = Serial.readStringUntil('\n');

    if (comando.startsWith("CONNECT")) {
      Serial.println("connected");
    } else if (comando.startsWith("CREDITS")) {
      Serial.println("created by danidoble");
    } else if (comando.startsWith("HI")) {
      Serial.println("hello there");
    } else {
      Serial.println("ara ara, what are you doing?");
    }
  }
}
```

## Device class

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

export class Arduino extends Core {
  constructor({
    filters = null,
    config_port = {
      baudRate: 9600,
      dataBits: 8,
      stopBits: 1,
      parity: "none",
      bufferSize: 32768,
      flowControl: "none",
    },
    no_device = 1,
  } = {}) {
    super({ filters, config_port, no_device });

    this.__internal__.device.type = "arduino";
    Devices.registerType("arduino");

    if (Devices.getByNumber(this.typeDevice, no_device)) {
      throw new Error(`Device arduino #${no_device} already exists`);
    }

    this.__internal__.time.response_connection = 2000;
    this.__internal__.time.response_general = 2000;
    this.__internal__.serial.delay_first_connection = 1000;

    Devices.add(this);
    this.getResponseAsString();
  }

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

  serialMessage(codex) {
    const message = {
      code: codex,
      name: "unknown",
      description: "Unknown command",
      request: "unknown",
      no_code: 400,
    };

    switch (codex) {
      case "connected":
        Object.assign(message, {
          name: "connected",
          description: "Connection established",
          request: "connect",
          no_code: 100,
        });
        break;
      case "created by danidoble":
        Object.assign(message, {
          name: "thanks",
          description: "Thanks for using this software",
          request: "credits",
          no_code: 101,
        });
        break;
      case "hello there":
        Object.assign(message, {
          name: "hello there",
          description: "Hi human",
          request: "hi",
          no_code: 102,
        });
        break;
    }

    this.dispatch("serial:message", message);
  }

  async sayCredits() {
    await this.appendToQueue(this.parseStringToBytes("CREDITS"), "credits");
  }

  async sayHi() {
    await this.appendToQueue(this.parseStringToBytes("HI"), "hi");
  }

  async sendCustomCode({ code = "" } = {}) {
    if (typeof code !== "string") throw new TypeError("code must be a string");
    await this.appendToQueue(this.parseStringToBytes(code), "custom");
  }
}
```

## Connection script

```javascript
// main.js
import { Arduino } from "./arduino.js";

export const arduino = new Arduino();

arduino.on("serial:message", (event) => {
  console.log("[message]", event.detail);
});

arduino.on("serial:timeout", (event) => {
  console.warn("[timeout]", event.detail);
});

arduino.on("serial:error", (event) => {
  console.error("[error]", event.detail);
});

arduino.on("serial:disconnected", () => {
  document.getElementById("log").textContent += "Disconnected\n";
  toggleUI(false);
});

arduino.on("serial:connecting", () => {
  document.getElementById("log").textContent += "Connecting…\n";
});

arduino.on("serial:connected", () => {
  document.getElementById("log").textContent += "Connected!\n";
  toggleUI(true);
});

arduino.on("serial:need-permission", () => {
  document.getElementById("log").textContent +=
    "Permission required — click Connect\n";
});

arduino.on("serial:soft-reload", () => {
  // reset your local variables here
});

arduino.on("serial:unsupported", () => {
  document.getElementById("unsupported").hidden = false;
});

function toggleUI(connected) {
  document.getElementById("connect").hidden = connected;
  document.getElementById("disconnect").hidden = !connected;
}

function tryConnect() {
  arduino.connect().catch(console.error);
}

document.addEventListener("DOMContentLoaded", () => {
  tryConnect();
  document.getElementById("connect").addEventListener("click", tryConnect);
  document.getElementById("disconnect").addEventListener("click", async () => {
    await arduino.disconnect().catch(console.error);
    document.getElementById("log").textContent += "Disconnected by user\n";
  });
  document
    .getElementById("hi")
    .addEventListener("click", () => arduino.sayHi().catch(console.error));
  document
    .getElementById("credits")
    .addEventListener("click", () => arduino.sayCredits().catch(console.error));
});
```

## HTML

```html


  
    
    
    webserial-core v1 — Arduino demo
    
  
  
    

Arduino Demo

Log:
``` ## Multiple devices To manage two Arduino boards simultaneously: ```javascript import { Arduino } from "./arduino.js"; import { Devices } from "webserial-core"; const a1 = new Arduino({ no_device: 1 }); const a2 = new Arduino({ no_device: 2 }); // Connect both at once await Devices.connectToAll(); // Send a command to board 2 only await a2.sayHi(); ``` --- --- url: /v1/examples/socket-bridge.md --- # Socket.io Bridge example ::: warning Legacy v1 docs v1 is on security fixes only. For new projects use the [v2 WebSocket provider](/api/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](./arduino) 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`): | Parser | Property | Description | | -------------------- | -------------------------------- | ---------------------------------------------------- | | `inter-byte-timeout` | `socketPortParserInterval` (ms) | Emits after silence — good for text protocols | | `byte-length` | `socketPortParserLength` (bytes) | Emits every N bytes — good for fixed-frame protocols | ```javascript // inside your constructor: this.socketPortParser = "inter-byte-timeout"; this.socketPortParserInterval = 30; // ms ``` --- --- url: /es.md ---