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

@mastashake08/rnode-web

v1.0.9

Published

Browser-native Web Serial controller and Web Crypto layer for LoRa RNodes in KISS/TNC mode.

Readme

rnode-web

A browser-native, zero-dependency JavaScript library designed to interface directly with LoRa RNodes configured in KISS/TNC mode. By leveraging the modern Web Serial and Web Bluetooth (BLE) APIs, this package allows you to deploy client-side off-grid messaging apps, telemetry dashboards, or provisioning utilities straight from a standard web URL—completely free of local Python, pip, or compiled native driver toolchains.

It includes an integrated, hardware-accelerated encryption layer running on the browser's native Web Crypto API to keep your over-the-air payloads safe from external sniffers.


⚡ Features

  • 🔌 Dual-Transport Topology: Transparent runtime toggling between wired USB connections (Web Serial API) and wireless mobile links (Web Bluetooth API).
  • 🔒 End-to-End Encryption: Automated, hardware-accelerated AES-GCM (256-bit) data pipelining with unique 12-byte initialization vector (IV) injection per packet.
  • 📟 KISS State Machine Parser: Built-in byte boundary and character escaping (FEND/FESC) that gracefully stitches together fragmented asynchronous serial streams.
  • 📡 Hardware Configuration Interface: Direct abstraction methods to query and rewrite RNode transceiver values (Frequency, Spreading Factor, Bandwidth, Coding Rate, TX Power).
  • 🎛️ Reactive Event-Driven Architecture: Extends native browser EventTarget enabling clean decouple mechanics using declarative .addEventListener() hooks.

📦 Installation

Install the package into your project via npm:

npm install @mastashake08/rnode-web

Or consume it directly in native browser modules using an ESM CDN:

import { RNodeController } from '@mastashake08/rnode-web';

🚀 Quick Start Guide

1. Initialize and Manage Cryptography

The controller handles key derivation under the hood. You can seed the engine using a custom string passphrase or pass a cryptographically strong 32-byte binary array.

import { RNodeController } from '@mastashake08/rnode-web';

const rnode = new RNodeController();

// Initialize an encrypted session using a passphrase
await rnode.setPreSharedKey("My_OffGrid_Mesh_Secret_Password");

2. Establish Hardware Connections

⚠️ Browser Security Constraint: Both Web Serial and Web Bluetooth requests must be executed inside a direct user-interaction callback handler (such as a button click event).

Option A: Wired Connection (Web Serial)

Typically maps to standard desktop USB environments matching default RNode firmware baud rates.

document.getElementById('btn-connect-usb').addEventListener('click', async () => {
  try {
    // connectSerial(baudRate, flowControl)
    await rnode.connectSerial(115200, 'hardware');
    console.log("Wired RNode link established.");
  } catch (err) {
    console.error("Wired hook failed:", err);
  }
});

Option B: Wireless Connection (Web Bluetooth / BLE)

Maps to mobile browsers and tablets using the transparent Nordic UART Service (NUS) profile.

document.getElementById('btn-connect-ble').addEventListener('click', async () => {
  try {
    await rnode.connectBLE();
    console.log("Wireless BLE RNode link established.");
  } catch (err) {
    console.error("Bluetooth pairing or discovery failed:", err);
  }
});

ℹ️ Note on BLE Pairing: RNode implementations configure characteristics as protected attributes. When your app executes its first transmission or starts listening to notifications, the host operating system will automatically hijack focus and prompt the user for the RNode's 6-digit Bluetooth pairing PIN.


📡 Transmitting Data Over the Air

Once connected, your application can transmit across three different operational abstraction tiers:

// 1. Secure Text Transmission (AES-GCM 256-bit)
// Automatically prepends a random 12-byte IV, encrypts the text, and applies KISS framing.
await rnode.sendEncryptedText("Hello encrypted world!");

// 2. Cleartext Broadcast Transmission
// Transmits standard unencrypted UTF-8 text readable by any sniffer on the same frequency.
await rnode.sendPlaintext("Public broadcast message.");

// 3. Raw Hex / Protocol-Agnostic Buffer Transmission
// Transmits raw un-escaped bytes (e.g., custom data packet schemas, AX.25, or Reticulum frames)
const customBytes = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
await rnode.sendRaw(customBytes);

🎛️ Handling Inbound Streams & Events

The library abstracts all stream data sorting. Subscribe directly to the instance events to update your user interface in real-time:

Traffic Handlers

| Event Name | Event Object Details (e.detail) | Description | | --- | --- | --- | | 'decrypted-text' | { text, port, commandId } | Fires when an inbound secure packet is intercepted and successfully decrypted via your key. | | 'plaintext' | { text, port, commandId } | Fires when unencrypted readable plaintext arrives on the antenna. | | 'raw-data' | { payload, port, commandId } | Fires for every data packet, returning the raw unescaped Uint8Array binary layout. | | 'decryption-failed' | { payload, error, port } | Fires when encrypted frames arrive but fail to decode (due to corrupt bytes or a key mismatch). |

Connection & Configuration Status Handlers

| Event Name | Event Object Details (e.detail) | Description | | --- | --- | --- | | 'connected' | { type: 'serial'|'ble', ... } | Fires immediately when a hardware connection handoff resolves. | | 'disconnected' | { type, unexpected: true|false } | Fires on targeted closure or when an unprompted hardware disconnect occurs. | | 'config-frequency' | { hz, port } | Triggered when the node reads out or verifies its operational frequency. | | 'config-sf' | { sf, port } | Triggered when the node reports its active Spreading Factor. |

Code Implementation Example

rnode.addEventListener('decrypted-text', (e) => {
  const { text, port } = e.detail;
  appendMessageToChatWindow(`[Port ${port}] Secure Link: ${text}`);
});

rnode.addEventListener('plaintext', (e) => {
  appendMessageToChatWindow(`Public Broadcast: ${e.detail.text}`);
});

rnode.addEventListener('decryption-failed', () => {
  showNetworkAlertWarning("Encrypted packet dropped. Authentication token mismatch.");
});

rnode.addEventListener('disconnected', (e) => {
  if (e.detail.unexpected) {
    attemptAutomaticReconnectionRoutine();
  }
});

🛠️ Provisioning & Hardware Control

You can read or overwrite the transceiver configuration parameters at any point while the session interface is active.

// Align the radio modem to match your target grid
await rnode.setFrequency(915000000);        // Set to 915 MHz (US ISM Band)
await rnode.setBandwidth(125000);          // Set to 125 kHz 
await rnode.setSpreadingFactor(7);         // Set to SF7 (Faster data transfer speed)
await rnode.setCodingRate(5);              // Set to CR 4/5 error protection bounds
await rnode.setTxPower(20);                // Max out transmission gain to +20 dBm

🔐 Security Context (AES-GCM Protocol Layout)

When utilizing sendEncryptedText(), the binary payload packed inside the over-the-air LoRa data space adheres to the following sequence layout before passing through the final TNC-facing KISS encapsulation wrapper:

┌───────────────────────────────┬──────────────────────────────────────────────┐
│  Initialization Vector (IV)   │             Encrypted Ciphertext             │
│          (12 Bytes)           │               (Variable Length)              │
├───────────────────────────────┴──────────────────────────────────────────────┤
│ ◄─────────────────────────────── Encrypted Payload ────────────────────────► │
└──────────────────────────────────────────────────────────────────────────────┘
  1. Nonce Isolation: The 12-byte initialization vector (IV) is cryptographically unique per packet, generated using the hardware-backed window entropy generator crypto.getRandomValues().
  2. Symmetric Layer: The ciphertext block contains an explicit, appended cryptographic authentication tag automatically verified by crypto.subtle.decrypt() before processing, protecting the node from replay and manipulation tactics.

📝 License

This project is open-source software licensed under the MIT License.