From 64389519f995ce7dcb82b0200a710b71faa7e74a Mon Sep 17 00:00:00 2001 From: Timur Davydov Date: Sat, 14 Feb 2026 03:19:59 +0400 Subject: [PATCH 1/2] docs: add README for npm packages --- .npmignore | 159 ++++++++ LICENSE | 2 +- README.md | 29 +- clear_project.sh | 3 +- generate_rst_docs.sh | 21 ++ package.json | 9 +- packages/core/README.md | 217 +++++++++++ packages/core/package.json | 17 +- packages/core/src/index.ts | 1 + packages/core/src/transform/index.ts | 2 +- packages/frontend-core/README.md | 341 ++++++++++++++++++ packages/frontend-core/package.json | 17 +- packages/nestjs-microservice/README.md | 208 +++++++++++ packages/nestjs-microservice/package.json | 17 +- packages/vue3-components/README.md | 81 ++++- packages/vue3-components/package.json | 17 +- .../src/components/SdrInput.vue | 8 +- .../vue3-components/src/components/index.ts | 1 + test-apps/package.json | 6 + 19 files changed, 1127 insertions(+), 29 deletions(-) create mode 100644 .npmignore create mode 100755 generate_rst_docs.sh create mode 100644 packages/core/README.md create mode 100644 packages/frontend-core/README.md create mode 100644 packages/nestjs-microservice/README.md diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b1e79ee --- /dev/null +++ b/.npmignore @@ -0,0 +1,159 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Build outputs +dist/ +build/ +packages/frontend-core/control/ +*.tsbuildinfo + +# Auto-generated sources (created during packages/frontend-core prebuild) +packages/frontend-core/src/webusb/webUsbDevices.autogen.ts + +# Environment variables +.env +.env.local +.env.development +.env.development.local +.env.test.local +.env.production.local + +# IDE and Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov +.nyc_output/ + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Old files +/old + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json + +# Local Netlify folder +.netlify + +# Vite build tool cache +.vite + +# Vitest coverage +coverage/ + +# TypeScript cache +*.tsbuildinfo + +# Optional stylelint cache +.stylelintcache + +# SvelteKit build / generate output +.svelte-kit + +# Package manager lock files +# package-lock.json +# yarn.lock +# pnpm-lock.yaml + +# Turborepo cache +.turbo/ + +# GitHub Actions +.github/ + +# Test artifacts and reports +test-results/ +junit.xml + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/LICENSE b/LICENSE index 91cba6a..8d0d53d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Wavelet Lab +Copyright (c) 2025-2026 Wavelet Lab Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 324d8ad..47a7523 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,23 @@ Currently supported SDR devices: 3. [LimeSDR Mini v2](https://limesdr-mini.myriadrf.org/v2.2/) — tested with v2.2; should also work with v2.3 and v2.4. 4. [SSDR](https://www.crowdsupply.com/wavelet-lab/ssdr) 5. [XTRX](https://www.crowdsupply.com/fairwaves/xtrx) -6. RTLSDR - support is in progess +6. RTLSDR - support is in progress ## What is WebSDR? WebSDR contains utilities, UI components, backend modules, and small test apps to make it easier to build browser-based SDR applications and tooling. The primary goal is to enable interaction with SDR devices connected over USB from web applications (via WebUSB), and to provide supporting building blocks for dashboards, demos, and server-side microservices. Core capabilities include: -- [WebUSB device management](/docs/webusb/README.md) (requesting devices, selecting devices in UI components). +- [WebUSB device management](docs/webusb/README.md) (requesting devices, selecting devices in UI components). - A small Vue 3 component library for dashboards and controls (dropdowns, lists, inputs, log viewers). - NestJS modules for microservices (authentication, API scaffolding) useful for backend parts of an SDR web platform. - Utility modules: circular buffers, data conversion helpers, string utilities, time helpers and promise helpers used across frontend and backend. +## Documentation + +- Docs index: [docs/README.md](docs/README.md) +- WebUSB / SDR interaction subsystem: [docs/webusb/README.md](docs/webusb/README.md) + ## Repository layout Top-level structure (important folders): @@ -35,6 +40,7 @@ packages/ ├─ frontend-core/ # Front-end utilities and WebUSB adapters ├─ vue3-components/ # Reusable Vue 3 UI components and styles ├─ nestjs-microservice/ # NestJS modules (auth, API helpers, microservice wiring) +docs/ # Architecture and subsystem documentation test-apps/ # Small example/test applications and scripts ``` @@ -45,6 +51,23 @@ Brief package descriptions: - `packages/nestjs-microservice` — NestJS integration and helper modules; main entry is `WebSDRModule` (configurable via environment variables such as `WEBSDR_*`). - `test-apps` — Small scripts and demo pages used to test low-level functionality (e.g., `usb-test.ts`). +## Published npm packages + +This monorepo publishes several packages under the `@websdr/*` scope. If you only want to consume the libraries (not develop inside the monorepo), install them from npm. + +- `@websdr/core` — shared types/constants and small utilities. + - Docs: [packages/core/README.md](packages/core/README.md) + - Install: `npm install @websdr/core` +- `@websdr/frontend-core` — frontend utilities (API helpers, WebUSB abstraction). + - Docs: [packages/frontend-core/README.md](packages/frontend-core/README.md) + - Install: `npm install @websdr/frontend-core` +- `@websdr/vue3-components` — Vue 3 UI components + styles. + - Docs: [packages/vue3-components/README.md](packages/vue3-components/README.md) + - Install: `npm install @websdr/vue3-components` +- `@websdr/nestjs-microservice` — reusable NestJS modules (auth/users/logging). + - Docs: [packages/nestjs-microservice/README.md](packages/nestjs-microservice/README.md) + - Install: `npm install @websdr/nestjs-microservice` + ## Quick setup Install dependencies for the workspace: @@ -53,6 +76,8 @@ Install dependencies for the workspace: npm install ``` +If you only want to use the libraries as dependencies, see **Published npm packages** above. + Build the packages: ```bash diff --git a/clear_project.sh b/clear_project.sh index b7184fb..9574d3f 100755 --- a/clear_project.sh +++ b/clear_project.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Remove node_modules directories find . -type d -name "node_modules" -prune -exec rm -rf '{}' + @@ -6,6 +6,7 @@ find . -type d -name "node_modules" -prune -exec rm -rf '{}' + # Remove common distributive/build directories find . -type d -name "dist" -prune -exec rm -rf '{}' + find . -type d -name "coverage" -prune -exec rm -rf '{}' + +find . -type d -name "docs.build" -prune -exec rm -rf '{}' + # Remove lock files find . -type f -name "package-lock.json" -prune -exec rm -f '{}' + diff --git a/generate_rst_docs.sh b/generate_rst_docs.sh new file mode 100755 index 0000000..8ff6db7 --- /dev/null +++ b/generate_rst_docs.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +DIST_DIR="docs.build" + +# Create the distribution directory if it doesn't exist +mkdir -p "$DIST_DIR" + +# Generate .rst files from README.md files +pandoc -s -f gfm -t rst --wrap=preserve -o $DIST_DIR/websdr.rst README.md +pandoc -s -f gfm -t rst --wrap=preserve -o $DIST_DIR/core.rst packages/core/README.md +pandoc -s -f gfm -t rst --wrap=preserve -o $DIST_DIR/frontend-core.rst packages/frontend-core/README.md +pandoc -s -f gfm -t rst --wrap=preserve -o $DIST_DIR/nestjs-microservice.rst packages/nestjs-microservice/README.md +pandoc -s -f gfm -t rst --wrap=preserve -o $DIST_DIR/vue3-components.rst packages/vue3-components/README.md + +# Update links in the generated .rst files +sed -E -i \ + -e 's|`packages/([^/]+)/README.md.*`__|:doc:`/webdev/websdr/\1`|g' \ + -e 's|", "license": "MIT", + "homepage": "https://docs.wsdr.io/webdev/websdr/websdr.html", + "repository": { + "type": "git", + "url": "https://github.com/wavelet-lab/websdr" + }, "keywords": [ "radio", "websdr", @@ -15,7 +20,7 @@ "xsdr", "ssdr" ], - "publishConfig": { "access": "public" }, + "private": true, "type": "module", "workspaces": [ "./packages/core", @@ -42,4 +47,4 @@ "vite": "^7.3.1", "vitest": "^4.0.18" } -} +} \ No newline at end of file diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..f8fef04 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,217 @@ +# @websdr/core + +Core TypeScript utilities for the WebSDR ecosystem: shared types/constants, small runtime helpers, and fast sample format conversions. + +**What’s inside** +- **Common:** shared types + size/format constants. +- **Utils:** circular buffer, timing helpers, logging, promise helper, string helpers, journal types. +- **Transform:** PCM buffer converters used by DSP pipelines. + +## Install + +```bash +npm install @websdr/core +``` + +```bash +pnpm add @websdr/core +``` + +```bash +yarn add @websdr/core +``` + +## Importing + +This package is published as ESM (see `type: module`). Most users should import from the package root: + +```ts +import { CircularBuffer, SimpleLogger, bufferF32ToI16 } from '@websdr/core'; +``` + +Subpath exports are also available (often better for clarity / tree-shaking): + +```ts +import { CircularBuffer } from '@websdr/core/utils'; +import { bufferI16ToF32 } from '@websdr/core/transform'; +import { CHUNK_SIZE, DataType } from '@websdr/core/common'; +``` + +## Examples + +### CircularBuffer + +```ts +import { CircularBuffer } from '@websdr/core/utils'; + +const buf = new CircularBuffer(4); +buf.push_back(1); +buf.push_back(2); + +console.log(buf.size()); // 2 +console.log(buf.front()); // 1 +console.log(buf.back()); // 2 +``` + +### Timing helpers + +```ts +import { now, sleep, usleep } from '@websdr/core/utils'; + +const t0 = now(); +await usleep(10); // ms +await sleep(0.05); // seconds +console.log('elapsed', now() - t0); +``` + +### PCM conversions + +```ts +import { bufferF32ToI16, bufferI16ToF32, clipF32Buffer } from '@websdr/core/transform'; + +const f32 = new Float32Array([0.0, 0.5, -0.5]); + +// Optional: clip before converting if your pipeline can produce out-of-range values +clipF32Buffer(f32); + +const i16 = bufferF32ToI16(f32); +const f32roundtrip = bufferI16ToF32(i16); +``` + +If you want to avoid allocations in a hot path, reuse an output buffer: + +```ts +import { bufferF32ToI16 } from '@websdr/core/transform'; + +const out = new Int16Array(8192); + +function onAudioFrame(frame: Float32Array) { + // Converts up to min(frame.length, out.length) + bufferF32ToI16(frame, out); + return out; +} +``` + +### Common constants and types + +```ts +import { CHUNK_SIZE, COMPLEX_FLOAT_SIZE, DataType } from '@websdr/core/common'; + +const streamType: DataType = DataType.cf32; +const bytesPerChunk = CHUNK_SIZE * COMPLEX_FLOAT_SIZE; +console.log({ streamType, bytesPerChunk }); +``` + +### Logging + +```ts +import { SimpleLogger } from '@websdr/core/utils'; + +const logger = new SimpleLogger('websdr'); +logger.log('hello'); +logger.warn('something odd'); +logger.error('something bad'); +``` + +### PromiseHelper (request/response correlation) + +Useful when you send requests that later resolve from an event handler. + +```ts +import { PromiseHelper } from '@websdr/core/utils'; + +const promises = new PromiseHelper(); + +function sendRequest(payload: unknown) { + const [id, promise] = promises.createPromise<{ ok: boolean }>(); + transport.send({ id, payload }); + return promise; +} + +transport.on('message', (msg: { id: number; result?: unknown; error?: unknown }) => { + const entry = promises.getPromise(msg.id); + if (!entry) return; + promises.deletePromise(msg.id); + if (msg.error) promises.promiseReject(entry, msg.error); + else promises.promiseResolve(entry, msg.result); +}); +``` + +### Filtering helpers + +```ts +import { containsAnySubstr, stringToBoolean } from '@websdr/core/utils'; + +const enabled = stringToBoolean(process.env.DEBUG); +const allow = containsAnySubstr('usb:device connected', ['usb:', 'webusb'], false); +console.log({ enabled, allow }); +``` + +### Journal log items + +```ts +import { JournalLogLevel, timestampToTimeString } from '@websdr/core/utils'; +import type { JournalLogItem } from '@websdr/core/utils'; + +const item: JournalLogItem = { + timestamp: Date.now(), + subSystem: 'webusb', + logLevel: JournalLogLevel.INFO, + message: 'device opened', +}; + +console.log(`[${timestampToTimeString(item.timestamp)}] ${item.subSystem}: ${item.message}`); +``` + +## Public API (summary) + +- **`@websdr/core/common`**: `DataType`, `CHUNK_SIZE`, `FLOAT_SIZE`, `COMPLEX_FLOAT_SIZE`, `INT16_SIZE`, `COMPLEX_INT16_SIZE`. +- **`@websdr/core/utils`**: + - `CircularBuffer` + - `sleep`, `usleep`, `now`, `timestampToTimeString` + - `PromiseHelper` + - `JournalLogLevel`, `JournalLogLevelKeys`, `JournalLogItem` + - `SimpleLogger`, `LOG_LEVELS`, `LoggerInterface`, `LogLevel` + - `stringToBoolean`, `containsAnySubstr` +- **`@websdr/core/transform`**: `bufferF32ToI16`, `bufferI16ToF32`. +- **`@websdr/core/transform`**: `bufferF32ToI16`, `bufferI16ToF32`, `clipF32Buffer`. + +## Compatibility notes + +- **TypeScript:** ships `*.d.ts` typings. +- **Runtime:** uses `performance.now()` in `now()`. In browsers this is always available; in Node.js it depends on your Node version / environment. + +## Development + +From the repository root: + +```bash +npm install +``` + +From this package folder: + +### Build +```bash +npm run build +``` + +### Test +```bash +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/core/src/index.ts +- Common exports: https://github.com/wavelet-lab/websdr/blob/main/packages/core/src/common/index.ts +- Utils exports: https://github.com/wavelet-lab/websdr/blob/main/packages/core/src/utils/index.ts +- Transform exports: https://github.com/wavelet-lab/websdr/blob/main/packages/core/src/transform/index.ts + +Package folder (GitHub): +https://github.com/wavelet-lab/websdr/tree/main/packages/core + +## License + +WebSDR is [MIT licensed](https://github.com/wavelet-lab/websdr/blob/main/LICENSE) diff --git a/packages/core/package.json b/packages/core/package.json index 376a5f0..1387ce3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -4,6 +4,12 @@ "description": "This is the core package for WebSDR", "author": "Timur Davydov ", "license": "MIT", + "homepage": "https://docs.wsdr.io/webdev/websdr/core.html", + "repository": { + "type": "git", + "url": "https://github.com/wavelet-lab/websdr", + "directory": "packages/core" + }, "keywords": [ "circular buffer", "conversion", @@ -21,7 +27,9 @@ "build": "tsc && tsc-alias && tsc-esm-fix", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "prepack": "cp ../../LICENSE ./LICENSE || true", + "postpack": "rm -f ./LICENSE" }, "devDependencies": { "@types/node": "^25.2.3" @@ -47,5 +55,10 @@ "import": "./dist/utils/index.js", "require": "./dist/utils/index.js" } - } + }, + "files": [ + "dist", + "LICENSE", + "README.md" + ] } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e49dba5..78a3b8b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,4 @@ // Re-export all core functionalities export * from './common'; +export * from './transform'; export * from './utils'; diff --git a/packages/core/src/transform/index.ts b/packages/core/src/transform/index.ts index 96bfaee..3a364d3 100644 --- a/packages/core/src/transform/index.ts +++ b/packages/core/src/transform/index.ts @@ -1,2 +1,2 @@ // Re-exporting converter functions -export { bufferF32ToI16, bufferI16ToF32 } from './converters'; \ No newline at end of file +export { bufferF32ToI16, bufferI16ToF32, clipF32Buffer } from './converters'; \ No newline at end of file diff --git a/packages/frontend-core/README.md b/packages/frontend-core/README.md new file mode 100644 index 0000000..e53d73d --- /dev/null +++ b/packages/frontend-core/README.md @@ -0,0 +1,341 @@ +# @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 + +```bash +npm install @websdr/frontend-core +``` + +```bash +pnpm add @websdr/frontend-core +``` + +```bash +yarn add @websdr/frontend-core +``` + +## Importing + +This package is published as ESM (see `type: module`). + +Import from the root: + +```ts +import { apiFetch, setApiBase, ensureWebUsb } from '@websdr/frontend-core'; +``` + +Or use subpath exports: + +```ts +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. + +```ts +import { setApiBase, apiFetch } from '@websdr/frontend-core/services'; + +setApiBase('http://localhost:3000'); + +type Profile = { id: string; username: string }; +const profile = await apiFetch('/api/auth/profile'); +``` + +You can also set the API base via a global variable (useful for `index.html` deployments): + +```ts +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`): + +```ts +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) + +```ts +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: + +```ts +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`). + +```ts +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). + +```ts +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`. + +```ts +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). + +```ts +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); +``` + +## 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: + +```bash +npm install +``` + +From this package folder: + +### Build +```bash +npm run prebuild +npm run build +``` + +### Test +```bash +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](https://github.com/wavelet-lab/websdr/blob/main/LICENSE) diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index f8286a3..35f38b3 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -4,6 +4,12 @@ "description": "This is the core frontend package for WebSDR", "author": "Timur Davydov ", "license": "MIT", + "homepage": "https://docs.wsdr.io/webdev/websdr/frontend-core.html", + "repository": { + "type": "git", + "url": "https://github.com/wavelet-lab/websdr", + "directory": "packages/frontend-core" + }, "keywords": [ "radio", "websdr", @@ -26,7 +32,9 @@ "prebuild": "node ./scripts/prebuild.js", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "prepack": "cp ../../LICENSE ./LICENSE || true", + "postpack": "rm -f ./LICENSE" }, "dependencies": { "@websdr/core": "^0.5.0", @@ -58,5 +66,10 @@ "import": "./dist/webusb/index.js", "require": "./dist/webusb/index.js" } - } + }, + "files": [ + "dist", + "LICENSE", + "README.md" + ] } diff --git a/packages/nestjs-microservice/README.md b/packages/nestjs-microservice/README.md new file mode 100644 index 0000000..b0898fd --- /dev/null +++ b/packages/nestjs-microservice/README.md @@ -0,0 +1,208 @@ +# @websdr/nestjs-microservice + +Reusable NestJS building blocks for the WebSDR backend. + +This package is published as an npm module and is intended to be **embedded into your NestJS app** (import modules, reuse guards/services), not run as a standalone server by itself. + +## What’s inside + +- **Auth** (`/auth`): `AuthModule`, `AuthService`, `JwtAuthGuard`, DTOs and interfaces. +- **Users** (`/users`): `UsersModule` and `UsersService`. +- **Common** (`/common`): a small logging helper module (`LoggingModule`, `createContextLogger`) and `LoggerLevelService`/`parseLogLevels`. + +## Install + +```bash +npm install @websdr/nestjs-microservice +``` + +```bash +pnpm add @websdr/nestjs-microservice +``` + +```bash +yarn add @websdr/nestjs-microservice +``` + +## Importing + +This package is published as ESM (see `type: module`). + +Import from the root: + +```ts +import { AuthModule, UsersModule, JwtAuthGuard } from '@websdr/nestjs-microservice'; +``` + +Or use subpath exports: + +```ts +import { AuthModule, JwtAuthGuard } from '@websdr/nestjs-microservice/auth'; +import { UsersModule } from '@websdr/nestjs-microservice/users'; +import { LoggingModule, LOGGER } from '@websdr/nestjs-microservice/common'; +``` + +## Usage + +### 1) Import modules in your Nest app + +```ts +import { Module } from '@nestjs/common'; +import { AuthModule, UsersModule } from '@websdr/nestjs-microservice'; + +@Module({ + imports: [UsersModule, AuthModule], +}) +export class AppModule {} +``` + +### 2) Protect controllers with `JwtAuthGuard` + +`JwtAuthGuard` validates a JWT (Passport strategy) and also checks token revocation via `AuthService.isRevoked()`. +It looks for the token in either: +- `req.cookies.jwt`, or +- `Authorization: Bearer ` + +```ts +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { JwtAuthGuard } from '@websdr/nestjs-microservice/auth'; + +@Controller('private') +export class PrivateController { + @UseGuards(JwtAuthGuard) + @Get() + getPrivateData() { + return { ok: true }; + } +} +``` + +Accessing the authenticated user (`req.user`) with proper typing: + +```ts +import { Controller, Get, Req, UseGuards } from '@nestjs/common'; +import type { AuthRequest } from '@websdr/nestjs-microservice/auth'; +import { JwtAuthGuard } from '@websdr/nestjs-microservice/auth'; + +@Controller('me') +export class MeController { + @UseGuards(JwtAuthGuard) + @Get() + getMe(@Req() req: AuthRequest) { + return req.user; + } +} +``` + +### 3) Provide a logger instance via `LoggingModule` + +`LoggingModule.forRoot()` registers a logger instance under the `LOGGER` token. +`createContextLogger()` wraps a base logger and adds Nest-like context. + +```ts +import { Module, Logger } from '@nestjs/common'; +import { LoggingModule } from '@websdr/nestjs-microservice/common'; + +@Module({ + imports: [LoggingModule.forRoot(new Logger())], +}) +export class AppModule {} +``` + +Injecting the base logger and creating a context-aware wrapper: + +```ts +import { Inject, Injectable } from '@nestjs/common'; +import type { LoggerService } from '@nestjs/common'; +import { LOGGER, createContextLogger } from '@websdr/nestjs-microservice/common'; + +@Injectable() +export class DeviceService { + private readonly logger: LoggerService; + + constructor(@Inject(LOGGER) base: any) { + this.logger = createContextLogger(base, DeviceService.name); + } + + open() { + this.logger.log('opening device'); + } +} +``` + +### 4) Configure log levels + +`parseLogLevels()` parses strings like `"debug,warn,error"`, as well as `"all"`/`"on"` and `"off"`/`"false"`. + +```ts +import { Logger } from '@nestjs/common'; +import { parseLogLevels } from '@websdr/nestjs-microservice/common'; + +Logger.overrideLogger(parseLogLevels(process.env.LOG_LEVELS)); +``` + +If you prefer a service that can update levels at runtime: + +```ts +import { NestFactory } from '@nestjs/core'; +import { LoggerLevelService } from '@websdr/nestjs-microservice/common'; +import { AppModule } from './app.module'; + +const app = await NestFactory.create(AppModule); +app.get(LoggerLevelService).setLevelsFromString(process.env.LOG_LEVELS); +await app.listen(3000); +``` + +## Public API (summary) + +- **`@websdr/nestjs-microservice/auth`**: + - `AuthModule` + - `JwtAuthGuard` + - DTOs: `LoginDto` + - Types: `AuthUser`, `AuthRequest` +- **`@websdr/nestjs-microservice/users`**: + - `UsersModule` +- **`@websdr/nestjs-microservice/common`**: + - `LoggingModule`, `LOGGER`, `createContextLogger` + - `LoggerLevelService`, `parseLogLevels`, `LoggerLevels` + +## Compatibility notes + +- **NestJS:** built against NestJS v11. +- **TypeScript:** ships `*.d.ts` typings. +- **ESM:** package is ESM. If your app is CommonJS, use a bundler/transpiler setup that supports ESM dependencies. + +## Development + +From the repository root: + +```bash +npm install +``` + +From this package folder: + +### Build +```bash +npm run build +``` + +### Test +```bash +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/nestjs-microservice/src/index.ts +- Auth exports: https://github.com/wavelet-lab/websdr/blob/main/packages/nestjs-microservice/src/auth/index.ts +- Common exports: https://github.com/wavelet-lab/websdr/blob/main/packages/nestjs-microservice/src/common/index.ts +- Users exports: https://github.com/wavelet-lab/websdr/blob/main/packages/nestjs-microservice/src/users/index.ts + +Package folder (GitHub): +https://github.com/wavelet-lab/websdr/tree/main/packages/nestjs-microservice + +## License + +WebSDR is [MIT licensed](https://github.com/wavelet-lab/websdr/blob/main/LICENSE) diff --git a/packages/nestjs-microservice/package.json b/packages/nestjs-microservice/package.json index dab525d..be7738d 100644 --- a/packages/nestjs-microservice/package.json +++ b/packages/nestjs-microservice/package.json @@ -4,6 +4,12 @@ "description": "This is a NestJS microservice for WebSDR", "author": "Timur Davydov ", "license": "MIT", + "homepage": "https://docs.wsdr.io/webdev/websdr/nestjs-microservice.html", + "repository": { + "type": "git", + "url": "https://github.com/wavelet-lab/websdr", + "directory": "packages/nestjs-microservice" + }, "keywords": [ "nestjs", "microservice", @@ -22,7 +28,9 @@ "build": "tsc && tsc-alias && tsc-esm-fix", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "prepack": "cp ../../LICENSE ./LICENSE || true", + "postpack": "rm -f ./LICENSE" }, "dependencies": { "@nestjs/common": "^11.1.13", @@ -64,5 +72,10 @@ "import": "./dist/users/index.js", "require": "./dist/users/index.js" } - } + }, + "files": [ + "dist", + "LICENSE", + "README.md" + ] } diff --git a/packages/vue3-components/README.md b/packages/vue3-components/README.md index a16bd3e..b8ceca2 100644 --- a/packages/vue3-components/README.md +++ b/packages/vue3-components/README.md @@ -1,13 +1,53 @@ -# Vue 3 Components +# @websdr/vue3-components -A collection of Vue 3 components with customizable styling using CSS variables. +Vue 3 UI components for the WebSDR ecosystem (Dropdown, virtualized List, LogArea, SdrInput) with customizable styling via CSS variables. -## Installation +## Install ```bash npm install @websdr/vue3-components ``` +```bash +pnpm add @websdr/vue3-components +``` + +```bash +yarn add @websdr/vue3-components +``` + +## Importing + +Import from the root: + +```ts +import { Dropdown, DropdownOption, List, LogArea, SdrInput } from '@websdr/vue3-components'; +``` + +Or from subpaths: + +```ts +import { Dropdown, List } from '@websdr/vue3-components/components'; +import { calculateDisplayItems } from '@websdr/vue3-components/utils'; +``` + +## Styles + +Import the compiled CSS (recommended): + +```ts +import '@websdr/vue3-components/styles/index.css'; +``` + +You can also import per-component CSS: + +```ts +import '@websdr/vue3-components/styles/dropdown.css'; +import '@websdr/vue3-components/styles/list.css'; +``` + +Styling is based on CSS custom properties (variables). Override them in your app’s CSS to match your design system. + ## Components ### Dropdown @@ -244,11 +284,11 @@ const isProcessing = ref(false) - `disabled?: boolean` — When true the input is disabled. Defaults to `false`. **Emits:** -- `update::device` (RequestDeviceInfo | undefined) — Emitted when the selected device changes. Use `v-model:device` to bind. +- `update:device` (RequestDeviceInfo) — Standard Vue event for `v-model:device`. **Behavior / Notes:** - When opened the component calls the WebUSB manager (`getWebUsbManagerInstance`) to request a device. -- If a device is returned it becomes the selected device and `update::device` is emitted with the `RequestDeviceInfo`. +- If a device is returned it becomes the selected device and `update:device` is emitted with the `RequestDeviceInfo`. - If the picker is cancelled the component emits an empty device object (fields set to empty/0) to indicate no selection. - The visual status (error/success) is derived from whether a device name is present. @@ -264,12 +304,14 @@ All components are designed for maximum customization using CSS custom propertie ### Quick Start -Import the base variables to get started: +Import the compiled CSS entry (recommended for npm consumers): -```scss -@import '@websdr/vue3-components/src/styles/variables.scss'; +```ts +import '@websdr/vue3-components/styles/index.css'; ``` +If you need to customize via CSS variables, define overrides in your application stylesheet (no Sass required). The sections below list the available variables. + ### Component Customization Each component exposes specific CSS variables for granular control: @@ -594,6 +636,14 @@ This approach ensures that your components integrate seamlessly with any design ## Development +From the repository root: + +```bash +npm install +``` + +From this package folder: + ### Build ```bash npm run build @@ -601,9 +651,20 @@ npm run build ### Test ```bash -npm run 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/vue3-components/src/index.ts +- Components exports: https://github.com/wavelet-lab/websdr/blob/main/packages/vue3-components/src/components/index.ts +- Styles entry (SCSS): https://github.com/wavelet-lab/websdr/blob/main/packages/vue3-components/src/styles/index.scss +- Utils exports: https://github.com/wavelet-lab/websdr/blob/main/packages/vue3-components/src/utils/index.ts + +Package folder (GitHub): +https://github.com/wavelet-lab/websdr/tree/main/packages/vue3-components + ## License -MIT \ No newline at end of file +WebSDR is [MIT licensed](https://github.com/wavelet-lab/websdr/blob/main/LICENSE) diff --git a/packages/vue3-components/package.json b/packages/vue3-components/package.json index f1fbe15..9a7296f 100644 --- a/packages/vue3-components/package.json +++ b/packages/vue3-components/package.json @@ -4,6 +4,12 @@ "description": "This is a Vue 3 components package for WebSDR", "author": "Timur Davydov ", "license": "MIT", + "homepage": "https://docs.wsdr.io/webdev/websdr/vue3-components.html", + "repository": { + "type": "git", + "url": "https://github.com/wavelet-lab/websdr", + "directory": "packages/vue3-components" + }, "keywords": [ "vue", "vue3", @@ -26,7 +32,9 @@ "build:types": "vue-tsc --emitDeclarationOnly", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "prepack": "cp ../../LICENSE ./LICENSE || true", + "postpack": "rm -f ./LICENSE" }, "dependencies": { "@websdr/core": "^0.5.0", @@ -64,5 +72,10 @@ "require": "./dist/utils/index.cjs.js" }, "./styles/*": "./dist/styles/*" - } + }, + "files": [ + "dist", + "LICENSE", + "README.md" + ] } diff --git a/packages/vue3-components/src/components/SdrInput.vue b/packages/vue3-components/src/components/SdrInput.vue index 86ac7dc..1e04155 100644 --- a/packages/vue3-components/src/components/SdrInput.vue +++ b/packages/vue3-components/src/components/SdrInput.vue @@ -10,7 +10,7 @@ export default defineComponent({ }); export interface SdrInputProps { - device: RequestDeviceInfo; // Currently selected SDR device + device?: RequestDeviceInfo; // Currently selected SDR device mode?: 'single' | 'worker'; // Mode of WebUsb manager operation placeholder?: string; // Placeholder text for the input size?: SizeType; // Size of the input @@ -26,7 +26,7 @@ import { getWebUsbManagerInstance, WebUsbManagerMode } from '@websdr/frontend-co import type { StatusType } from './components.d'; interface Emits { - (e: 'update::device', requestDevice: RequestDeviceInfo): void; + (e: 'update:device', requestDevice: RequestDeviceInfo): void; } const props = withDefaults(defineProps(), { @@ -51,12 +51,12 @@ async function selectUsb(requestDevice: RequestDeviceInfo | undefined) { if (requestDevice !== undefined) { Object.assign(value, requestDevice); // console.log('setstorage', device) - emit('update::device', value); + emit('update:device', value); } else { value.devName = ''; value.vendorId = 0; value.productId = 0; - emit('update::device', value); + emit('update:device', value); } } diff --git a/packages/vue3-components/src/components/index.ts b/packages/vue3-components/src/components/index.ts index eaf15dc..784c4d7 100644 --- a/packages/vue3-components/src/components/index.ts +++ b/packages/vue3-components/src/components/index.ts @@ -3,6 +3,7 @@ export { default as LogArea } from "./LogArea.vue"; export type { LogItemKey, LogAreaProps } from './LogArea.vue'; export { default as Dropdown } from './Dropdown.vue'; export type { DropdownProps, DropdownOptionProps } from './Dropdown.vue'; +export { default as DropdownOption } from './DropdownOption.vue'; export { default as List } from './List.vue'; export type { ListProps } from './List.vue'; export { default as SdrInput } from './SdrInput.vue'; diff --git a/test-apps/package.json b/test-apps/package.json index c4444c0..2a32ee4 100644 --- a/test-apps/package.json +++ b/test-apps/package.json @@ -4,6 +4,12 @@ "description": "WebSDR test applications", "author": "Timur Davydov ", "license": "MIT", + "homepage": "https://github.com/wavelet-lab/websdr/tree/main/test-apps", + "repository": { + "type": "git", + "url": "https://github.com/wavelet-lab/websdr", + "directory": "test-apps" + }, "keywords": [ "radio", "websdr", From 74ccd773840236c29c3204c2d97c474dca334465 Mon Sep 17 00:00:00 2001 From: Timur Davydov Date: Sun, 15 Feb 2026 03:55:11 +0400 Subject: [PATCH 2/2] npm: remove tests from archives --- packages/core/package.json | 1 + packages/frontend-core/package.json | 1 + packages/nestjs-microservice/package.json | 1 + packages/vue3-components/package.json | 1 + packages/vue3-components/src/tests/SdrInput.test.ts | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 1387ce3..fee8877 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,7 @@ }, "files": [ "dist", + "!dist/tests", "LICENSE", "README.md" ] diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index 35f38b3..f977bdb 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -69,6 +69,7 @@ }, "files": [ "dist", + "!dist/tests", "LICENSE", "README.md" ] diff --git a/packages/nestjs-microservice/package.json b/packages/nestjs-microservice/package.json index be7738d..532c9be 100644 --- a/packages/nestjs-microservice/package.json +++ b/packages/nestjs-microservice/package.json @@ -75,6 +75,7 @@ }, "files": [ "dist", + "!dist/tests", "LICENSE", "README.md" ] diff --git a/packages/vue3-components/package.json b/packages/vue3-components/package.json index 9a7296f..d9b39a0 100644 --- a/packages/vue3-components/package.json +++ b/packages/vue3-components/package.json @@ -75,6 +75,7 @@ }, "files": [ "dist", + "!dist/tests", "LICENSE", "README.md" ] diff --git a/packages/vue3-components/src/tests/SdrInput.test.ts b/packages/vue3-components/src/tests/SdrInput.test.ts index 9521144..23b5af4 100644 --- a/packages/vue3-components/src/tests/SdrInput.test.ts +++ b/packages/vue3-components/src/tests/SdrInput.test.ts @@ -69,7 +69,7 @@ describe('SdrInput.vue', () => { expect((getWebUsbManagerInstance as any).mock.calls.length).toBeGreaterThan(0) expect((getWebUsbManagerInstance as any).mock.calls[0][0]).toBe(WebUsbManagerMode.SINGLE) - const emitted = wrapper.emitted()['update::device'] + const emitted = wrapper.emitted()['update:device'] expect(emitted).toBeTruthy() expect((emitted![0] as any)[0]).toBeDefined() expect((emitted![0] as any)[0].devName).toBe('USB-Device')