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

@ourbackyard/p2p-sdk

v0.2.0

Published

Modular P2P SDK — WebRTC mesh networking with pluggable signaling

Readme

@ourbackyard/p2p-sdk

Modular, framework-agnostic P2P SDK — WebRTC mesh networking with pluggable signaling.

Quick Start (10 lines)

import { P2PNode, NostrSignaling, MemoryStorage } from '@ourbackyard/p2p-sdk';

const node = new P2PNode({
  peerId:    'alice-abc123',
  signaling: new NostrSignaling({ peerId: 'alice-abc123', h3Cell: '8928308280fffff' }),
  storage:   new MemoryStorage(),
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

await node.start();
node.on('peer:connected', peer => console.log('Connected:', peer));
node.on('message',        msg  => console.log('Msg from', msg.from, ':', msg));
node.broadcast('HELLO', { text: 'World' });

Architecture

┌────────────────────────────────────────────────┐
│                  P2PNode                       │  ← orchestrator / event facade
│  (event-bus + wires all modules together)      │
└──────────┬──────────────────────┬──────────────┘
           │                      │
    ┌──────▼──────┐        ┌──────▼──────────────┐
    │  Signaling  │        │  WebRTCTransport     │
    │  (ISignaling│        │  (RTCPeerConnection  │
    │   interface)│        │   + DataChannel)     │
    │             │        └──────────┬───────────┘
    │ NostrSignal │                   │
    │  - or -     │           ┌───────▼──────────┐
    │ WSSignaling │           │  MessageRouter    │
    └─────────────┘           │  (type dispatch   │
                              │   + dedup)        │
                              └─────┬───────┬─────┘
                                    │       │
                          ┌─────────▼─┐ ┌───▼──────────┐
                          │ GossipSync│ │ BlobTransfer  │
                          │ (item      │ │ (binary blob  │
                          │  broadcast │ │  streaming)   │
                          │  + sync)   │ └───────────────┘
                          └─────┬──────┘
                                │
                         ┌──────▼──────┐
                         │  IStorage   │  ← inject Dexie, localStorage, or MemoryStorage
                         └─────────────┘

                     ┌───────────────────────┐
                     │     E2ECrypto         │  ← ECDH P-256 + AES-GCM 256
                     │  (per-peer shared key)│      used by P2PNode._wireCrypto()
                     └───────────────────────┘

Module Descriptions

| Module | File | Responsibility | |--------|------|----------------| | P2PNode | src/index.js | Top-level orchestrator; composes all modules | | EventBus | src/event-bus.js | Tiny typed event emitter (no Node.js dep) | | WebRTCTransport | src/transport/webrtc-transport.js | RTCPeerConnection + DataChannel lifecycle | | ISignaling | src/signaling/signaling-interface.js | Interface contract all signaling must implement | | NostrSignaling | src/signaling/nostr-signaling.js | Decentralised signaling over public Nostr relays | | WebSocketSignaling | src/signaling/websocket-signaling.js | Centralised signaling over your own WS server | | MessageRouter | src/sync/message-router.js | Type-based routing + deduplication | | GossipSync | src/sync/gossip-sync.js | Item broadcast & incremental sync via gossip | | BlobTransfer | src/sync/blob-transfer.js | Chunked binary streaming with backpressure | | E2ECrypto | src/crypto/e2e-crypto.js | ECDH P-256 key exchange → AES-GCM 256 | | IStorage | src/storage/storage-interface.js | Storage adapter interface | | MemoryStorage | src/storage/memory-storage.js | In-memory implementation for dev/testing | | utils | src/utils.js | Pure helpers: uuid, ab2hex, hex2ab, sha256hex |


Signaling Options

| | NostrSignaling | WebSocketSignaling | |---|---|---| | Infrastructure | None (uses public Nostr relays) | Requires your own WS server | | Decentralisation | ✅ Fully decentralised | ❌ Single point (but server only relays) | | TURN distribution | ❌ (add separate TURN server) | ✅ Server can push TURN credentials | | Cold-start latency | ~1-4 s (relay connect) | ~50-200 ms | | Reliability | Depends on relay uptime (7 defaults) | Depends on your server | | Best for | Open/community apps, no infra | Production deployments |

NostrSignaling config

new NostrSignaling({
  peerId:   'alice',
  h3Cell:   '8928308280fffff',   // H3 L9 hex cell (geographic channel)
  relays:   ['wss://relay.damus.io'],  // optional, defaults to 7 public relays
  secp256k1: window.secp256k1,         // optional injected module for real Schnorr sigs
})

WebSocketSignaling config

new WebSocketSignaling({
  url:    'wss://signal.example.com',
  peerId: 'alice',
  roomId: 'calgary-h3-cell',    // optional room partitioning
})

Minimal server protocol (relay JSON over WS):

Client → { type:'announce', peerId, roomId, meta }
Client → { type:'signal',   target, from, signal }

Server → { type:'signal',      from, signal }
Server → { type:'announce',    peerId, meta }
Server → { type:'peer-joined', peerId }
Server → { type:'peer-left',   peerId }
Server → { type:'ice-config',  config: { iceServers, ttl, expiresAt } }

Implementing a Custom IStorage

import { IStorage } from '@ourbackyard/p2p-sdk/storage';

class DexieStorage extends IStorage {
  constructor(db) { super(); this.db = db; }

  async getItems(since, limit = 100) {
    return this.db.items.where('timestamp').above(since)
      .reverse().limit(limit).toArray();
  }
  async addItem(item) { await this.db.items.add(item); }
  async hasItem(sellerId, timestamp) {
    return !!(await this.db.items.where('timestamp').equals(timestamp)
      .filter(i => i.sellerId === sellerId).first());
  }
  async hasItemByTitle(sellerId, title) {
    return !!(await this.db.items.where('title').equals(title)
      .filter(i => i.sellerId === sellerId).first());
  }
  async updateItemStatus(itemId, status) {
    await this.db.items.where('itemId').equals(itemId).modify({ status });
  }

  async getBlob(hash)          { return this.db.blobs.where('hash').equals(hash).first() ?? null; }
  async addBlob(hash, blob, meta) { await this.db.blobs.add({ hash, blob, ...meta, timestamp: Date.now() }); }
  async hasBlob(hash)          { return !!(await this.db.blobs.where('hash').equals(hash).first()); }
  async getMissingBlobHashes(hashes) {
    const have = new Set((await this.db.blobs.where('hash').anyOf(hashes).toArray()).map(b => b.hash));
    return hashes.filter(h => !have.has(h));
  }

  async addChatMessage(msg)        { await this.db.chatMessages.put(msg); }
  async getChatMessage(id)         { return this.db.chatMessages.where('id').equals(id).first() ?? null; }
  async markRead(msgId, readAt)    { await this.db.chatMessages.where('id').equals(msgId).modify({ read: true, readAt }); }

  async addDeadDrop(toPeerId, msg) {
    const id = crypto.randomUUID();
    await this.db.deadDrop.add({ id, toPeerId, msg, createdAt: Date.now(), delivered: false });
    return id;
  }
  async getPendingDeadDrop(toPeerId) {
    return this.db.deadDrop.where('toPeerId').equals(toPeerId).filter(r => !r.delivered).toArray();
  }
  async markDelivered(id) { await this.db.deadDrop.update(id, { delivered: true, deliveredAt: Date.now() }); }
}

Implementing a Custom ISignaling

import { ISignaling } from '@ourbackyard/p2p-sdk/signaling';

class MySignaling extends ISignaling {
  async connect()                         { /* open channel */ this.emit('status', 'online'); }
  async disconnect()                      { /* close channel */ this.emit('status', 'offline'); }
  async sendSignal(targetPeerId, signal)  { /* relay signal */ }
  async announce(meta = {})              { /* broadcast presence */ }
  get isOnline()                          { return /* true if connected */; }
}

// When a signal arrives from the channel:
//   this.emit('signal', fromPeerId, signal)
// When a peer announces:
//   this.emit('peer:announce', peerId, meta)

Migration Guide from p2p-mesh.js

| Old API | SDK equivalent | |---------|----------------| | new OurBackyardMesh({ peerId, h3Cell, db }) | new P2PNode({ peerId, signaling, storage, iceServers }) | | mesh.init() | node.start() | | mesh.broadcastItem(item) | node.broadcastItem(item) | | mesh.on('item', cb) | node.on('item:new', cb) | | mesh.on('chat', cb) | Register via node._router.handle('CHAT', cb) | | mesh.sendChat(toPeerId, text) | node.send(toPeerId, 'CHAT', { text }) | | mesh.destroy() | node.stop() | | Hardcoded ICE servers | Pass iceServers: [...] to P2PNode | | Direct Dexie calls | Implement DexieStorage extends IStorage | | window.* bridge hacks | None — SDK is environment-agnostic |

Known bugs fixed vs p2p-mesh.js

  • Line 260: log && log(...)log was undefined; replaced with console.log
  • ICE servers: Removed hardcoded openrelay.metered.ca credentials
  • All window.* access: Removed; SDK works in Workers and Node.js with polyfills

Zero External Dependencies

The SDK uses only browser-native APIs:

  • RTCPeerConnection / RTCDataChannel — WebRTC
  • crypto.subtle — ECDH key exchange, AES-GCM encryption, SHA-256 hashing
  • WebSocket — signaling channels

Optional injected dependencies:

  • secp256k1 — for real Schnorr signatures in NostrSignaling (relay may reject events without it)
  • Any IStorage implementation (Dexie, localStorage, etc.)---

License

MIT