npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@danidoble/webserial-jsd

v1.0.0

Published

A strongly-typed, event-driven JSD Jofemar vending machine serial device driver built on top of webserial-core.

Readme

@danidoble/webserial-jsd

TypeScript driver for Jofemar JSD vending machines over serial port, built on top of webserial-core v2.

Handles serial connection, binary handshake, automatic reconnection and message routing — exposing only clean, typed events.

Not tied to a single transport: use the WebUSB, Web Bluetooth or WebSocket provider from webserial-core, or implement your own SerialProvider.

npm version license


Requirements

  • webserial-core ^2.1.0 (peer dependency)
  • A compatible transport (see Providers):
    • Web Serial API — Chrome / Edge 89+ (default, no extra setup)
    • WebUSB — Chrome / Edge (via WebUsbProvider)
    • Web Bluetooth — Chrome / Edge (via createBluetoothProvider, Nordic UART Service)
    • WebSocket — any environment (via createWebSocketProvider + a bridge server)
    • Custom — any platform via your own SerialProvider implementation

Installation

# npm
npm install @danidoble/webserial-jsd webserial-core

# pnpm
pnpm add @danidoble/webserial-jsd webserial-core

# yarn
yarn add @danidoble/webserial-jsd webserial-core

# bun
bun add @danidoble/webserial-jsd webserial-core

webserial-core is a peer dependency — it must be installed alongside this package.


Quick start

import { JSD } from '@danidoble/webserial-jsd';

const jsd = new JSD({ transport: 'rs232' });

jsd.on('vision:machine-status', ({ machine, status }) => {
  console.log(`Machine ${machine}: ${status}`);
});

await jsd.connect();

Serial settings

The constructor pre-configures the following defaults — no extra setup needed:

| Setting | Value | | ------------------ | ------------------------ | | Baud rate | 9600 | | Data bits | 8 | | Stop bits | 1 | | Parity | none | | Flow control | none | | Buffer size | 255 B | | Parser | interByteTimeout (40 ms) | | Command timeout | 1 000 ms | | Auto-reconnect | ✓ | | Reconnect interval | 1 500 ms | | Handshake timeout | 2 000 ms |

Providers

By default the library uses the browser's native Web Serial API (navigator.serial). You can replace this with any of the built-in providers from webserial-core, or write your own.

Web Serial API (default)

No setup required — works out of the box in Chrome / Edge 89+.

import { jsd } from '@danidoble/webserial-jsd';

const jsd = new jsd({ filters: [{ usbVendorId: 0x2341 }] });
await jsd.connect();

WebUSB (WebUsbProvider)

Use the WebUSB API as the transport. Useful for devices or platforms where the native Web Serial API is unavailable, or when targeting CP210x / vendor-specific USB chips.

import { jsd, WebUsbProvider } from '@danidoble/webserial-jsd';

const jsd = new jsd({
  filters: [{ usbVendorId: 0x2341 }],
  provider: new WebUsbProvider()
});

await jsd.connect();

Web Bluetooth (createBluetoothProvider)

Communicate over Bluetooth Low Energy using the Nordic UART Service (NUS). The device must expose NUS characteristics.

import { jsd, createBluetoothProvider } from '@danidoble/webserial-jsd';

const jsd = new jsd({
  provider: createBluetoothProvider()
});

await jsd.connect(); // shows the browser Bluetooth picker

WebSocket (createWebSocketProvider)

Route serial communication through a WebSocket bridge server — ideal for Node.js environments or remote devices. A reference bridge implementation is available in the webserial-core demos.

import { jsd, createWebSocketProvider } from '@danidoble/webserial-jsd';

const jsd = new jsd({
  filters: [{ usbVendorId: 0x2341 }],
  provider: createWebSocketProvider('ws://localhost:8080')
});

await jsd.connect();

Global provider (AbstractSerialDevice.setProvider)

Set a provider once for all device instances instead of per-instance. Import AbstractSerialDevice directly from webserial-core:

import { AbstractSerialDevice, WebUsbProvider } from 'webserial-core';
import { jsd } from '@danidoble/webserial-jsd';

AbstractSerialDevice.setProvider(new WebUsbProvider());

const jsd = new jsd({ filters: [{ usbVendorId: 0x2341 }] });
await jsd.connect();

Custom provider

Implement the SerialProvider interface to target any platform:

import type { SerialProvider, SerialPortFilter } from '@danidoble/webserial-jsd';
import { jsd } from '@danidoble/webserial-jsd';

const myProvider: SerialProvider = {
  async requestPort(options?: { filters?: SerialPortFilter[] }): Promise<SerialPort> {
    // return a SerialPort-compatible object
  },
  async getPorts(): Promise<SerialPort[]> {
    // return previously authorised ports
  }
};

const jsd = new jsd({
  filters: [{ usbVendorId: 0x2341 }],
  provider: myProvider
});

Constructor

new JSD(options?: JSDOptions)

JSDOptions

| Property | Type | Default | Description | | ----------------- | --------------------------------- | --------- | ------------------------------------------------------- | | filters | SerialPortFilter[] | [] | Serial port filters to narrow the port picker. | | transport | 'rs232' \| 'tcpip' | 'rs232' | Transport strategy. | | provider | SerialProvider | — | Alternative provider (WebUSB, WebSocket, Bluetooth). | | polyfillOptions | SerialDeviceOptions<Uint8Array> | — | Extra options that override the internal configuration. |


Connection API

Inherited from AbstractSerialDevice in webserial-core.

await jsd.connect(); // Opens the port and runs the handshake.
await jsd.disconnect(); // Closes the port.
await jsd.forget(); // Releases the port permission.
jsd.isConnected(); // boolean — true if the port is open.
jsd.isDisconnected(); // boolean — true if the port is closed.

Sub-modules

jsd.vision

Vision machine management. Handles statuses, dispensing, channels and configuration.

Machine status

jsd.vision.getMachineStatus(machine: 1|2|3|4): MachineStatus | null

Returns the last known status of the machine.

jsd.vision.isAvailableToDispense({ machine?: 1|2|3|4 }): boolean

true if the machine is connected, in service, door closed and available to dispense.

await jsd.vision.requestMachineStatus({ machine?: 1|2|3|4 }): Promise<void>

Requests the current machine status. Emits vision:machine-status.

Channels and selections

await jsd.vision.requestStatusChannel({ machine, selection }): Promise<void>

Requests the status of a channel (selection 1-80 is mapped to tray/channel internally). Emits vision:channel-status.

await jsd.vision.assignChannels({ machine }): Promise<SelectionStatus[]>

Requests the status of all 80 channels of a machine sequentially. Returns an array with each selection's status when complete. Blocks concurrent re-assignment.

await jsd.vision.requestStatusSelection({ selection }): Promise<void>

Requests the status of a selection (1-240). Emits vision:status-selection.

await jsd.vision.requestChannelsLinkedToSelection({ selection }): Promise<void>

Requests the channels linked to a selection. Emits vision:channels-linked-to-selection.

Dispensing

await jsd.vision.dispense({ machine, selection, speed?, timePostRun?, cart? }): Promise<boolean>

Dispenses from a selection (1-81). cart groups the result with other cart commands. Resolves true on success, false on failure.

await jsd.vision.dispenseCart(data: { machine, selection }[]): Promise<{ selection, dispensed }[]>

Dispenses multiple selections in batches of 10. Waits for machines to become available between batches. Emits vision:waiting-collection if a machine is waiting for collection.

await jsd.vision.dispenseFromChannel({ machine, tray, channel, speed, timePostRun, cart? }): Promise<boolean>

Dispenses directly by tray and channel. Emits vision:dispense-status.

await jsd.vision.dispenseFromChannelExtended({
  machine, tray, channel, speed, timePostRun,
  fragileOrHeavy, typeAdjustElevator, timeAdjustElevator, cart?
}): Promise<boolean>

Extended version with elevator adjustment and fragile/heavy product options. Emits vision:extended-dispense-status-data.

await jsd.vision.dispenseFromSelection({ selection, cart? }): Promise<boolean>

Dispenses directly by JSD system selection number. Emits vision:dispense-status.

await jsd.vision.requestDispenseStatusFromChannel({ token }): Promise<void>
await jsd.vision.requestDispenseStatusFromSelection({ token }): Promise<void>
await jsd.vision.requestDispenseStatusFromChannelExtended({ token }): Promise<void>

Query the status of an ongoing dispense by token.

Selection configuration

await jsd.vision.configureSelectionDispense({ selection, speed, timePostRun }): Promise<void>

Configures the dispense parameters for a selection. Emits vision:selection-dispense-config.

await jsd.vision.requestSelectionDispenseConfig({ machine }): Promise<void>

Requests the current dispense configuration of a machine's selections. Emits vision:selection-dispense-config.

await jsd.vision.configureAddChannelToSelection({ selection, machine, tray, channel }): Promise<void>

Adds a channel to a selection.

Identification and version

await jsd.vision.requestMachineIdentification({ machine }): Promise<void>

Requests the machine identifier. Emits vision:machine-id.

await jsd.vision.requestJSDVersion(): Promise<void>

Requests the JSD firmware version. Emits vision:jsd-version.

Temperature

await jsd.vision.configureWorkingTemperature({ machine, temperature, enable }): Promise<void>
await jsd.vision.requestWorkingTemperature({ machine }): Promise<void>

Configures or requests the working temperature. Emits vision:current-temperature.

Timings

await jsd.vision.configureWaitingTimings({ collectPosition, dispenseManoeuvres, afterPickup }): Promise<void>
await jsd.vision.requestWaitingTimings(): Promise<void>

Configures or requests wait timings. Emits vision:time-waiting-for-product-collection.

await jsd.vision.configureTimeWaitingAfterPickup({ time }): Promise<void>
await jsd.vision.requestTimeWaitingAfterPickup(): Promise<void>

Configures or requests the post-pickup wait time. Emits vision:time-waiting-after-product-collection.

Lights

await jsd.vision.lightsOn({ machine }): Promise<void>
await jsd.vision.lightsOff({ machine }): Promise<void>

Faults and reset

await jsd.vision.requestReportActiveFaults({ machine }): Promise<void>

Requests the active faults report. Emits vision:active-faults.

await jsd.vision.requestReportInactiveFaults({ machine }): Promise<void>

Requests the inactive faults report. Emits vision:alarm-faults-events.

await jsd.vision.clearInactiveFaults({ machine }): Promise<void>
await jsd.vision.resetSoldOutChannels({ machine }): Promise<void>
await jsd.vision.resetFaultsAndSelfTest({ machine }): Promise<void>

Clears inactive faults, resets sold-out channels or runs a self-test.

await jsd.vision.resetAllErrors({ machine }): Promise<void[]>

Runs clearInactiveFaults, resetSoldOutChannels and resetFaultsAndSelfTest in parallel.

await jsd.vision.restartJSD(): Promise<void>

⚠️ DANGEROUS — Fully restarts the JSD device. Emits vision:jsd-status-reset.

Collection

await jsd.vision.collect({ machine }): Promise<void>

Starts the product collection cycle. Emits vision:collect.


jsd.manifest

Device manifest and log management.

await jsd.manifest.requestLogsEvent({ previous: boolean }): Promise<void>

Requests log events. previous: true retrieves earlier logs.

await jsd.manifest.requestLogs(): Promise<string[]>

Requests and returns all logs as a string array. Handles batches automatically. Throws if a request is already in progress.

await jsd.manifest.requestLogsByDate({ since: Date, until: Date }): Promise<void>

Requests logs in a date range. Emits manifest:log.

await jsd.manifest.requestForSendingManifest({ fileSizeBytes: number, crc: string }): Promise<void>

Prepares the device to receive a manifest file.

await jsd.manifest.sendManifestDataBlock({ prevBlockId: number, dataBlock: Uint8Array }): Promise<void>

Sends a manifest data block. Emits manifest:block and manifest:completed when finished.


jsd.licensing

Device license management.

await jsd.licensing.requestFeatureStatus({ feature: number }): Promise<void>

Queries the status of a licensed feature. Emits licensing:feature-status.

await jsd.licensing.requestTemporaryLicenseStatus(): Promise<void>

Queries the temporary license status. Emits licensing:temporary-license-status.

await jsd.licensing.requestSeedData(): Promise<void>

Requests the seed data needed to generate a license. Emits licensing:seed.

await jsd.licensing.requestLicenseActivation({ license: string }): Promise<void>

Activates a license by passing the base64 string. Emits licensing:feature-status.


Additional getter

jsd.transport: string  // 'rs232' | 'tcpip'

Events

All events are listened to with jsd.on(event, handler).

Connection events (inherited from webserial-core)

| Event | Payload | When | | ------------------------ | ---------------------- | --------------------------------- | | serial:connecting | instance | connect() started | | serial:connected | instance | Port open and handshake completed | | serial:disconnected | instance | Port closed | | serial:reconnecting | instance | Automatic reconnect cycle started | | serial:data | Uint8Array, instance | Frame received from device | | serial:sent | Uint8Array, instance | Bytes sent to device | | serial:error | Error, instance | Read/write error | | serial:need-permission | instance | User cancelled the port picker | | serial:queue-empty | instance | Command queue empty | | serial:timeout | Uint8Array, instance | Command timeout |

JSD protocol events

| Event | Payload | Description | | ------------------ | ---------------------- | ------------------------------ | | serial:jsd-data | VisionResponse | Parsed frame received | | serial:jsd-ack | { packetId: number } | ACK received | | serial:jsd-nack | { packetId: number } | NACK received | | serial:jsd-error | { message: string } | Protocol error | | serial:message | { message: string } | Informational protocol message |

Vision events

| Event | Payload | Description | | -------------------------------------------------- | ------------------------------------------ | ----------------------------------- | | vision:machine-status | MachineStatus & { machine } | Machine status updated | | vision:channels | { machine, channels: SelectionStatus[] } | Status of all channels | | vision:channels-progress | { machine, progress: number } | Channel assignment progress (0-100) | | vision:channel-status | SelectionStatus & { machine } | Status of an individual channel | | vision:status-selection | { selection, status } | Status of a selection | | vision:dispense-status | DispenseToken & { dispensed: boolean } | Dispense result | | vision:selection-dispense-config | { machine, config } | Selection dispense configuration | | vision:channels-linked-to-selection | StatusChannelsLinkedToSelection | Channels linked to a selection | | vision:machine-id | { machine, id: string } | Machine identifier | | vision:current-temperature | { machine, temperature: string } | Current temperature | | vision:alarm-faults-events | { machine, faults } | Alarms and faults (inactive) | | vision:active-faults | { machine, faults } | Active faults | | vision:active-faults-list | { machine, faults } | Active faults list | | vision:time-waiting-for-product-collection | { collectPosition, dispenseManoeuvres } | Wait timings | | vision:time-waiting-after-product-collection | { time } | Post-pickup wait time | | vision:collect | { machine } | Collection cycle completed | | vision:reset-sold-out-channels | { machine } | Sold-out channels reset | | vision:jsd-version | { version: string } | Firmware version | | vision:jsd-dispensing-queue | { queue } | JSD dispense queue | | vision:special-characteristics-selection | { selection, characteristics } | Special selection characteristics | | vision:perishable-products-config | { config } | Perishable products configuration | | vision:jsd-status-reset | JsdResetStatus | JSD reset status | | vision:extended-dispense-status-data | { token, data } | Extended dispense data | | vision:trays-positioning-phototransistors-status | { machine, status } | Phototransistors status | | vision:jsd-license-error | { error } | JSD license error | | vision:dispensing | { machine, token } | Dispense in progress | | vision:waiting-collection | { machine } | Machine waiting for collection | | vision:door | { machine, open: boolean } | Door status | | vision:connection | { machine, connected: boolean } | Connection with machine | | vision:wrong-cmd | { opcode, params } | Wrong command received |

Manifest events

| Event | Payload | Description | | -------------------- | --------------------- | ---------------------------- | | manifest:log | { log: string } | Log entry received | | manifest:block | { blockId: number } | Manifest block sent/received | | manifest:completed | { logs: string[] } | Manifest transfer completed | | manifest:wrong-cmd | { opcode, params } | Wrong command received |

Licensing events

| Event | Payload | Description | | ------------------------------------ | -------------------------------------- | ------------------------------- | | licensing:feature-status | { feature: number, active: boolean } | Licensed feature status | | licensing:seed | { seed: string } | Seed data to generate a license | | licensing:temporary-license-status | { active: boolean, remaining } | Temporary license status | | licensing:wrong-cmd | { opcode, params } | Wrong command received |


Full example

import { JSD } from '@danidoble/webserial-jsd';

const jsd = new JSD({
  transport: 'rs232',
  filters: [{ usbVendorId: 0x0403 }] // FTDI
});

jsd.on('vision:machine-status', ({ machine, status, availabilityToDispense }) => {
  console.log(`Machine ${machine}: ${status} / ${availabilityToDispense}`);
});

jsd.on('vision:dispense-status', ({ dispensed }) => {
  console.log(dispensed ? 'Dispensed OK' : 'Dispense failed');
});

jsd.on('serial:error', err => console.error(err));

await jsd.connect();

if (jsd.vision.isAvailableToDispense({ machine: 1 })) {
  const ok = await jsd.vision.dispense({ machine: 1, selection: 5 });
  console.log('Result:', ok);
}

TypeScript

All events and method signatures are fully typed. The package ships with .d.mts / .d.cts declaration files — no extra @types package required.

Commonly used types and all built-in providers are re-exported so you do not need to import directly from webserial-core:

// Main class
import { JSD, WebUsbProvider, createBluetoothProvider, createWebSocketProvider } from '@danidoble/webserial-jsd';

export type {
  JSDOptions,
  SerialPortFilter,
  SerialDeviceOptions,
  SerialEventMap,
  SerialParser,
  SerialProvider,
  SerialPolyfillOptions
} from '@danidoble/webserial-jsd';

License

GPL-3.0-only © Danidoble