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

@websdr/frontend-core

v0.5.3

Published

This is the core frontend package for WebSDR

Downloads

172

Readme

@websdr/frontend-core

Frontend-focused TypeScript core for the WebSDR ecosystem.

This package provides:

  • Small frontend common helpers (debug flag, NNG-over-WebSocket client, WASM errno enum).
  • Minimal HTTP API helpers for browser apps.
  • A WebUSB control + streaming layer used by WebSDR-compatible devices.

Install

npm install @websdr/frontend-core
pnpm add @websdr/frontend-core
yarn add @websdr/frontend-core

Importing

This package is published as ESM (see type: module).

Import from the root:

import { apiFetch, setApiBase, ensureWebUsb } from '@websdr/frontend-core';

Or use subpath exports:

import { debug_mode, NngWebSocket, Protocol } from '@websdr/frontend-core/common';
import { apiFetch, setApiBase } from '@websdr/frontend-core/services';
import { ensureWebUsb, WebUsbManagerMode, getWebUsbManagerInstance } from '@websdr/frontend-core/webusb';

Examples

API helpers

apiFetch() builds a URL from the configured base, includes cookies (credentials: 'include'), and throws on non-OK responses.

import { setApiBase, apiFetch } from '@websdr/frontend-core/services';

setApiBase('http://localhost:3000');

type Profile = { id: string; username: string };
const profile = await apiFetch<Profile>('/api/auth/profile');

You can also set the API base via a global variable (useful for index.html deployments):

import { apiFetch } from '@websdr/frontend-core/services';

(globalThis as any).__API_BASE__ = 'http://localhost:3000';
const profile = await apiFetch('/api/auth/profile');

Error handling (JSON errors are provided via Error.cause):

import { apiFetch } from '@websdr/frontend-core/services';

try {
  await apiFetch('/api/auth/profile');
} catch (e) {
  const err = e as any;
  console.error(err.message);
  if (err.cause) console.error('cause:', err.cause);
}

NNG-over-WebSocket (REQ/SUB)

import { NngWebSocket, Protocol } from '@websdr/frontend-core/common';

const ws = new NngWebSocket({
  url: 'ws://localhost:8000/ws',
  protocol: Protocol.SUB,
});

await ws.open();
ws.addEventListener('message', (ev) => {
  // handle events emitted by the class
});

REQ example (request/response). The send() promise resolves when a reply with the same request id arrives:

import { NngWebSocket, Protocol } from '@websdr/frontend-core/common';

const ws = new NngWebSocket({
  url: 'ws://localhost:8000/rpc',
  protocol: Protocol.REQ,
  binaryType: NngWebSocket.TEXT,
});

await ws.open();
const reply = await ws.send('ping', 1000);
console.log('reply:', reply);

WebUSB: ensure implementation + request a device

In browsers, navigator.usb exists when WebUSB is supported. In Node.js, you can use a polyfill implementation. ensureWebUsb() attempts to provide navigator.usb (prefers the usb package, falls back to webusb).

import {
  ensureWebUsb,
  WebUsbManagerMode,
  getWebUsbManagerInstance,
} from '@websdr/frontend-core/webusb';

await ensureWebUsb();

const mgr = getWebUsbManagerInstance(WebUsbManagerMode.SINGLE);
const picked = await mgr.requestDevice(); // requires user gesture in browsers
if (!picked) throw new Error('No device selected');

const fd = await mgr.open(picked.vendorId, picked.productId, picked.device);

const name = await mgr.getName(fd);
const serial = await mgr.getSerialNumber(fd);
console.log({ name, serial });

await mgr.close(fd);

WebUSB: control + streaming via ControlWebUsb

ControlWebUsb is a high-level helper built on top of WebUsbManager. It prepares structured control commands (connect/discover/params/stream control).

import { CHUNK_SIZE, DataType } from '@websdr/core/common';
import {
  ensureWebUsb,
  WebUsbManagerMode,
  getWebUsbManagerInstance,
  ControlWebUsb,
  WebUsbChannels,
  WebUsbDirection,
} from '@websdr/frontend-core/webusb';

const mode = WebUsbManagerMode.SINGLE;

await ensureWebUsb();
const mgr = getWebUsbManagerInstance(mode);

const picked = await mgr.requestDevice(); // requires user gesture
if (!picked) throw new Error('No device selected');

const fd = await mgr.open(picked.vendorId, picked.productId, picked.device);
if (fd < 0) throw new Error('Failed to open device');

const control = new ControlWebUsb({ mode });
await control.open(fd);

await control.sendCommand('CONNECT');
const discovered = await control.sendCommand('DISCOVER');
console.log('discover:', discovered);
const info = await control.getDeviceInfo(false);
console.log('device:', info);

await control.sendCommand('SET_RX_FREQUENCY', { chans: WebUsbChannels.CHAN1, frequency: 100e6 });
await control.sendCommand('SET_RX_GAIN', { chans: WebUsbChannels.CHAN1, gain: 15 });

// Prepare streaming (device-specific firmware decides how these map to actual stream state)
await control.sendCommand('START_STREAMING', {
  chans: WebUsbChannels.CHAN1,
  samplerate: 1e6,
  packetsize: CHUNK_SIZE,
  mode: WebUsbDirection.RX_TX,
  dataformat: DataType.ci16,
});

console.log('stream status:', await control.getStreamStatus());

await control.sendCommand('STOP_STREAMING');
await control.sendCommand('DISCONNECT');

await control.close();
await mgr.close(fd);

WebUSB: receive RX packets via WebUsbManager

submitRxPacket() requests one RX packet worth of IQ samples and returns a decoded RXBuffer.

import { DataType } from '@websdr/core/common';
import {
  ensureWebUsb,
  WebUsbManagerMode,
  getWebUsbManagerInstance,
} from '@websdr/frontend-core/webusb';

await ensureWebUsb();
const mgr = getWebUsbManagerInstance(WebUsbManagerMode.SINGLE);

const picked = await mgr.requestDevice();
if (!picked) throw new Error('No device selected');

const fd = await mgr.open(picked.vendorId, picked.productId, picked.device);
if (fd < 0) throw new Error('Failed to open device');

// Drivers may adjust your requested sample count (alignment, framing, etc.)
const cfg = await mgr.getConfiguration(fd);
const samples = await mgr.getRXSamplesCount(fd, cfg.defaultSamplesCount);

const rx = await mgr.submitRxPacket(fd, samples, {
  datatype: DataType.ci16,
  extra_meta: true,
  id: 1,
});

if (rx.datatype === DataType.ci16) {
  // Complex int16 IQ is typically interleaved: I0,Q0,I1,Q1,...
  const iq = new Int16Array(rx.data);
  const i0 = iq[0];
  const q0 = iq[1];
  console.log({ i0, q0, samples: rx.samples, ts: rx.timestamp });
} else if (rx.datatype === DataType.cf32) {
  const iq = new Float32Array(rx.data);
  const i0 = iq[0];
  const q0 = iq[1];
  console.log({ i0, q0, samples: rx.samples, ts: rx.timestamp });
}

await mgr.close(fd);

WebUSB: transmit TX packets via WebUsbManager

sendTxPacket() encodes and sends an IQ buffer to the device. In many device firmwares TX requires the stream to be started first (for example via ControlWebUsb commands).

import { DataType } from '@websdr/core/common';
import {
  ensureWebUsb,
  WebUsbManagerMode,
  getWebUsbManagerInstance,
} from '@websdr/frontend-core/webusb';

await ensureWebUsb();
const mgr = getWebUsbManagerInstance(WebUsbManagerMode.SINGLE);

const picked = await mgr.requestDevice();
if (!picked) throw new Error('No device selected');

const fd = await mgr.open(picked.vendorId, picked.productId, picked.device);
if (fd < 0) throw new Error('Failed to open device');

// A tiny dummy complex waveform (I/Q int16). Fill with real signal in your app.
const iq = new Int16Array(2 * 1024);
iq[0] = 0x1000;
iq[1] = 0;

const tx = await mgr.sendTxPacket(
  fd,
  {
    data: iq.buffer,
    byteOffset: iq.byteOffset,
    byteLength: iq.byteLength,
    datatype: DataType.ci16,
    discard_timestamp: true,
    timestamp: 0n,
  },
  { allowDrop: false }
);

console.log('tx status:', tx.usbOutTransferResult?.status);
await mgr.close(fd);

Using WebSDR with Vite (WASM Configuration)

@websdr/frontend-core includes an Emscripten-based WebAssembly module (control.wasm) that is loaded at runtime next to its corresponding JavaScript file.

When using Vite, additional configuration is required in development mode.


Why this is necessary

In dev mode, Vite performs dependency pre-bundling (optimizeDeps) and moves JavaScript dependencies into:

However, the WebAssembly file (control.wasm) is loaded dynamically by the Emscripten runtime and is not automatically copied into .vite/deps/.

As a result, the browser may attempt to load:

This file does not exist, leading to: Error requesting USB device: RuntimeError: Aborted(CompileError: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 3c 21 44 4f @+0)


Recommended Vite configuration

To prevent Vite from pre-bundling @websdr/frontend-core, add the following to your vite.config.ts:

import { defineConfig } from 'vite'

export default defineConfig({
  ...
  optimizeDeps: {
    exclude: [
      '@websdr/frontend-core',
    ],
  },
  ...
})

Public API (summary)

  • @websdr/frontend-core/common:
    • debug_mode
    • Protocol, NngWebSocket
    • WASMErrno
  • @websdr/frontend-core/services:
    • setApiBase, getApiBase, apiUrl, apiFetch
    • login, logout, getProfile
  • @websdr/frontend-core/webusb (high level):
    • ensureWebUsb
    • ControlWebUsb, WebUsbChannels, ControlWebUsbInitialParams
    • WebUsbManager, WebUsbManagerMode, getWebUsbManagerInstance
    • registerWebUsbInstance, getWebUsbInstance, SDRDevicesIds
    • WebUSB primitives and types: WebUsb, WebUsbEndpoints, DeviceStreamType, DeviceDataType, etc.

Notes / caveats

  • WebUSB device registrations: @websdr/frontend-core/webusb auto-imports webUsbDevices.autogen. When building from source inside the monorepo, this is generated by scripts/prebuild.js.
  • Node vs Browser: WebUSB is browser-native on supported platforms. For Node usage, ensureWebUsb() tries to load usb (preferred) or webusb dynamically.
  • User gesture requirement: navigator.usb.requestDevice() must be called in response to a user interaction (browser security requirement).

Development

From the repository root:

npm install

From this package folder:

Build

npm run prebuild
npm run build

Test

npm test

Source links

This package publishes dist/ to npm. Source is available in the GitHub repository:

  • Entry point: https://github.com/wavelet-lab/websdr/blob/main/packages/frontend-core/src/index.ts
  • Common exports: https://github.com/wavelet-lab/websdr/blob/main/packages/frontend-core/src/common/index.ts
  • Services exports: https://github.com/wavelet-lab/websdr/blob/main/packages/frontend-core/src/services/index.ts
  • WebUSB exports: https://github.com/wavelet-lab/websdr/blob/main/packages/frontend-core/src/webusb/index.ts

Package folder (GitHub): https://github.com/wavelet-lab/websdr/tree/main/packages/frontend-core

License

WebSDR is MIT licensed