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-pax

v1.0.0

Published

A strongly-typed, event-driven USB pax driver for the Web Serial API, built on top of webserial-core.

Readme

@danidoble/webserial-pax

A strongly-typed, event-driven PAX payment terminal driver built on top of webserial-core.

Handles the serial connection, JSON handshake, auto-reconnect, and message routing — so you only deal with clean, typed events.

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

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-pax webserial-core

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

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

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

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


Quick start

import { Pax } from '@danidoble/webserial-pax';
import type { SerialPortFilter } from '@danidoble/webserial-pax';

const filters: SerialPortFilter[] = [{ usbVendorId: 0x0ed3 }];
const pax = new Pax({ filters });

// Serial lifecycle
pax.on('serial:connecting',   () => console.log('Opening port…'));
pax.on('serial:connected',    () => console.log('Port open'));
pax.on('serial:disconnected', () => console.log('Disconnected'));

// PAX events
pax.on('pax:connected', ({ name }) => console.log('PAX ready:', name));
pax.on('pax:payment',   (data)     => console.log('Payment event:', data));
pax.on('pax:error',     (data)     => console.error('PAX error:', data));

// Opens a port picker dialog (requires a user gesture)
await pax.connect();

// Set credentials for MIT login
pax.server        = 'PROD';
pax.businessId    = 'your-business-id';
pax.encryptionKey = 'your-encryption-key';
pax.apiKey        = 'your-api-key';

// Perform a sale (blocks until terminal responds)
const approved = await pax.sendSale({ amount: 100.00, reference: 'ORDER-001' });
console.log('Approved:', approved);

// Retrieve a voucher
const voucher = await pax.sendVoucher({ folio: 12345 });
console.log('Voucher:', voucher);

// Refund
await pax.sendRefund({ amount: 100.00, folio: '12345', auth: '654321' });

Serial settings

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

| Setting | Value | | ------------------ | ------------------ | | Baud rate | 115 200 | | Data bits | 8 | | Stop bits | 1 | | Parity | none | | Flow control | none | | Buffer size | 32 768 B | | Parser | delimiter (\r\n) | | Command timeout | 5 000 ms | | Auto-reconnect | ✓ | | Reconnect interval | 1 500 ms | | Handshake timeout | 4 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 { Pax } from '@danidoble/webserial-pax';

const pax = new Pax({ filters: [{ usbVendorId: 0x0ed3 }] });
await pax.connect();

WebUSB (WebUsbProvider)

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

const pax = new Pax({
  filters: [{ usbVendorId: 0x0ed3 }],
  provider: new WebUsbProvider(),
});

await pax.connect();

Web Bluetooth (createBluetoothProvider)

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

const pax = new Pax({ provider: createBluetoothProvider() });
await pax.connect();

WebSocket (createWebSocketProvider)

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

const pax = new Pax({
  provider: createWebSocketProvider('ws://localhost:8080'),
});

await pax.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 { Pax } from '@danidoble/webserial-pax';

AbstractSerialDevice.setProvider(new WebUsbProvider());

const pax = new Pax({ filters: [{ usbVendorId: 0x0ed3 }] });
await pax.connect();

Custom provider

Implement the SerialProvider interface to target any platform:

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

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

const pax = new Pax({ provider: myProvider });
await pax.connect();

API

Constructor

new Pax(options?: PaxOptions)

| Option | Type | Default | Description | | ----------------- | --------------------- | ------- | ------------------------------------------------- | | filters | SerialPortFilter[] | [] | USB filters shown in the port picker dialog. | | provider | SerialProvider | — | Custom serial transport (WebUSB, WebSocket, etc). | | polyfillOptions | SerialDeviceOptions | — | Low-level serial settings override. |


Properties

pax.server        // get / set — 'PROD' | 'QA' | 'DEV'
pax.businessId    // get / set — string | null
pax.encryptionKey // get / set — string | null
pax.apiKey        // get / set — string | null

Methods

pax.connect()

Opens the port picker and performs the handshake. Inherited from AbstractSerialDevice.

pax.sendConnectMessage()

Sends a CONNECT command manually.

pax.sendSale(options)

Performs a full sale flow: login → init → payment. Resolves to true if approved, false otherwise. Throws if a sale is already in progress.

| Option | Type | Default | Description | | ----------- | ---------------- | ------- | -------------------------------------------- | | amount | number | 0 | Sale amount. Must be greater than 0. | | reference | string \| null | null | Alphanumeric reference (allows - and _). |

const approved = await pax.sendSale({ amount: 150.00, reference: 'ORDER-42' });

pax.sendVoucher(options)

Requests the last voucher by folio. Resolves to the voucher data or rejects on timeout (10 s).

| Option | Type | Description | | ------- | -------- | ---------------------------------------------- | | folio | number | Folio number returned after a successful sale. |

const voucher = await pax.sendVoucher({ folio: 12345 });

pax.sendRefund(options?)

| Option | Type | Default | Description | | -------- | -------------------------- | ------- | ---------------------------- | | amount | number | 0 | Refund amount (> 0). | | folio | number \| string \| null | null | Original transaction folio. | | auth | number \| string \| null | null | Original transaction auth. |

await pax.sendRefund({ amount: 150.00, folio: '12345', auth: '654321' });

pax.sendInfo()

Requests device information. Emits pax:info.

pax.sendKeepAlive()

Sends a keep-alive ping. Emits pax:keep-alive.

pax.sendRestartApp()

Requests the PAX app to restart. Emits pax:reset-app.

pax.sendGetConfig()

Requests the device configuration. Emits pax:get-config.

pax.sendHideButtons()

Hides the terminal's on-screen buttons.

pax.sendShowButtons()

Shows the terminal's on-screen buttons.

pax.sendDemo()

Triggers demo mode on the terminal.

pax.sendProductionQR()

Reads a QR code in production mode.

pax.sendQualityAssuranceQR()

Reads a QR code in QA mode.

pax.sendExit()

Sends an exit command to the terminal.

pax.sendCustomCode(obj)

Sends a raw JSON command. The object must have an action key.

await pax.sendCustomCode({ action: 'CUSTOM_CMD', extra: 'value' });

pax.softReload()

Resets in-progress sale / voucher state without reconnecting. Useful for recovering from stale operations.

pax.cancelSaleRequestInProcess()

Cancels an in-progress sale that is still awaiting a response.

pax.isConnected()

Returns true when the port is open and the handshake has completed.


Commands (static helpers)

All commands are available as standalone static methods for building raw payloads:

import { Commands } from '@danidoble/webserial-pax';

Commands.connection()                                          // CONNECT handshake
Commands.expectedResponseConnection()                         // expected CONNECT response
Commands.makeSale({ amount: 100 })                            // PAYMENT
Commands.makeSale({ amount: 100, reference: 'X' })            // PAYMENT with reference
Commands.getVoucher({ folio: 12345 })                         // GETVOUCHER
Commands.refund({ amount: 100, folio: '1', auth: '2' })       // REFUND
Commands.info()                                               // DEVICEINFO
Commands.keepAlive()                                          // KEEPALIVE
Commands.restartApp()                                         // RESETAPP
Commands.getConfig()                                          // GETCONFIG
Commands.hideButtons()                                        // HIDEBUTTONS
Commands.showButtons()                                        // SHOWBUTTONS
Commands.forceHide()                                          // FORCEHIDE
Commands.forceShow()                                          // FORCESHOW
Commands.demo()                                               // DEMO
Commands.exit()                                               // EXIT
Commands.init()                                               // INIT
Commands.readQR()                                             // READQR (production)
Commands.readQR({ type: 'QA' })                               // READQR (QA)
Commands.loginMit({ server, business_id, encryption_key, api_key })
Commands.custom({ action: 'MY_ACTION' })                      // custom payload

Events

Core events (from webserial-core)

| Event | Payload | Description | | ------------------------ | ----------------------------- | ------------------------------------------------- | | serial:connecting | instance | Port is being opened. | | serial:connected | instance | Port opened successfully. | | serial:disconnected | instance | Port closed or device unplugged. | | serial:reconnecting | instance | Auto-reconnect attempt in progress. | | serial:data | data: string, instance | Parsed string frame received from the device. | | serial:sent | data: string, instance | Data written to the port. | | serial:error | error: Error, instance | An error occurred during communication. | | serial:need-permission | instance | No authorised port found; user must grant access. | | serial:timeout | command: string, instance | A queued command timed out. |

PAX events

| Event | Payload | Description | | -------------------- | --------------------------------------- | ------------------------------------------------------------ | | pax:connected | { name, request, status } | Handshake succeeded; PAX is ready. | | pax:init | { name, request, status } | INIT response received. | | pax:init-app | { name, request, status } | INITAPP response received. | | pax:login | Record<string, unknown> | LOGIN response received. | | pax:voucher | Record<string, unknown> | LASTVOUCHER response received. | | pax:info | Record<string, unknown> | DEVICEINFO response received. | | pax:keep-alive | { name, request, status } | KEEPALIVE response received. | | pax:reset-app | { name, request, status } | RESETAPP response received. | | pax:get-config | Record<string, unknown> | GETCONFIG response received. | | pax:buttons-status | { name, request, hidden: boolean } | Button visibility changed. | | pax:payment | Record<string, unknown> | Any payment-related response (process, card data, result). | | pax:error | Record<string, unknown> | An ERROR response was received. | | pax:refund | Record<string, unknown> | REFUND response received. | | pax:message | Record<string, unknown> \| string | Emitted for every incoming message (parsed or raw string). |


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:

import {
  Pax,
  Commands,
  WebUsbProvider,
  createBluetoothProvider,
  createWebSocketProvider,
} from '@danidoble/webserial-pax';

import type {
  PaxOptions,
  MakeSaleOptions,
  GetVoucherOptions,
  RefundOptions,
  LoginMitOptions,
  ServerType,
  SerialPortFilter,
  SerialDeviceOptions,
  SerialEventMap,
  SerialProvider,
  SerialPolyfillOptions,
} from '@danidoble/webserial-pax';

License

GPL-3.0-only © Danidoble