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

@soapbox.pub/nostr-lora

v0.1.1

Published

Reference implementation of [NIP-LR](./NIP.md): Nostr over LoRa.

Readme

@soapbox.pub/nostr-lora

Reference implementation of NIP-LR: Nostr over LoRa.

Handles packet encoding/decoding, chunked transmission, reassembly, retransmission requests, and GM (announce) messages. Designed for use with MeshCore LoRa devices over Web Serial.

Structure

nostr-lora          # core — LoRaTransport, SerialConnectionManager, etc.
nostr-lora/react    # React hook — useNostrLora

React is an optional peer dependency. The core entry point has no React dependency.

Installation

npm install @soapbox.pub/nostr-lora

Usage

React

import { useNostrLora } from "nostr-lora/react";

function App() {
    const { connect, disconnect, isConnected, sendEvent, error } = useNostrLora(
        {
            onEvent(event) {
                console.log("received event", event);
            },
        },
    );

    return (
        <div>
            {isConnected
                ? <button onClick={disconnect}>Disconnect</button>
                : <button onClick={connect}>Connect</button>}
            {error && <p>{error.message}</p>}
        </div>
    );
}

connect() triggers the browser's serial port picker. Once connected, incoming Nostr events are delivered via onEvent. Call sendEvent(event) to broadcast a signed Nostr event over LoRa.

Full return type:

| Field | Type | Description | | ------------------ | ------------------------------------------------------ | ----------------------------------------- | | isConnected | boolean | Whether the serial port is open | | connecting | boolean | true while the port is being opened | | error | Error \| null | Last error, if any | | portLabel | string \| null | USB VID:PID of the connected device | | deviceName | string \| null | Device name from firmware | | sendQueue | QueueSnapshot[] | Current outbound packet queue | | lastPacket | PacketReceiveInfo \| null | SNR/RSSI/size of last received packet | | connect | () => Promise<void> | Open port and begin transport | | disconnect | () => Promise<void> | Close port | | sendEvent | (event: NostrEvent) => Promise<void> | Send a signed event | | sendGm | (nodeId, eventCount?, recentSince?) => Promise<void> | Broadcast a GM announcement | | setTimingOptions | ({ delay?, jitter? }) => void | Adjust inter-packet timing live | | transport | LoRaTransport \| null | Direct access to the underlying transport |

Vanilla (no framework)

import { LoRaTransport, SerialConnectionManager } from "nostr-lora";

const connection = new SerialConnectionManager();
const transport = new LoRaTransport(connection);

transport.on("event:receive", (event) => {
    console.log("received event", event);
});

transport.on("connect", (portLabel) => {
    console.log("connected to", portLabel);
});

transport.on("error", (err) => {
    console.error(err);
});

// Triggers the browser port picker
await transport.begin();

// Send a signed Nostr event
await transport.sendEvent(signedEvent);

// Later
await transport.end();

Custom connection

Implement ConnectionManager to use a different transport (e.g. WebSocket, BLE):

import type { ConnectionManager, ConnectionManagerEvents } from "nostr-lora";
import { LoRaTransport } from "nostr-lora";

class MyConnection implements ConnectionManager {
    on<E extends keyof ConnectionManagerEvents>(
        event: E,
        handler: (...args: ConnectionManagerEvents[E]) => void,
    ) {/* ... */}

    async open() {/* ... */}
    async close() {/* ... */}
    async sendRawData(data: Uint8Array) {/* ... */}
}

const transport = new LoRaTransport(new MyConnection());

Options

All options are optional. Defaults are exported as Defaults:

import { Defaults } from "nostr-lora";

| Option | Default | Description | | ------------------- | ----------- | ----------------------------------------------------------- | | nodeId | undefined | Your node's identity bytes (used in GM packets) | | logger | null | Object with log/warn/error/debug methods (e.g. console) | | interPacketDelay | 2000 ms | Base delay between packets in the send queue | | interPacketJitter | 500 ms | Max random jitter added on top of the delay | | requestInactivity | 30000 ms | Idle time before sending a retransmission request | | requestMaxRetries | 3 | Max retransmission attempts before abandoning | | eventTimeout | 30000 ms | Max wait for all chunks of a partial event | | dedupTimeout | 900000 ms | How long to remember received event IDs | | sentChunksTtl | 300000 ms | How long to keep sent chunks for potential retransmission | | initialTtl | 6 | Hop TTL assigned to locally-created events | | gmBackoffBase | 300000 ms | Minimum interval between GM responses to the same peer | | gmMaxShareEvents | 3 | Max events to re-share in response to a GM | | gmJitterMin | 500 ms | Min random delay before responding to a GM | | gmJitterMax | 1500 ms | Max random delay before responding to a GM |

Events

LoRaTransport extends EventEmitter and emits:

| Event | Args | Description | | ----------------- | ------------------------------------ | ------------------------------------------------ | | connect | portLabel: string | Connection opened | | disconnect | — | Connection closed | | error | err: Error | Error from connection or packet handling | | event:receive | event: NostrEvent | A complete Nostr event was received and verified | | event:send | event: NostrEvent | A Nostr event finished sending | | packet:receive | info: PacketReceiveInfo | Raw packet received (SNR, RSSI, size) | | chunk:receive | decoded, byteLength | Individual DATA chunk received | | gm:receive | decoded, byteLength | GM packet received | | request:receive | prefixHex, missingChunks | Retransmission request received from peer | | request:send | eventIdHex, missingChunks, attempt | Retransmission request sent | | queue:update | snapshot: QueueSnapshot[] | Send queue changed |

License

The source code of this library is provided to you under the terms of the MIT License.