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

@gethashd/bytecave-browser

v1.0.46

Published

ByteCave browser client for WebRTC P2P connections to storage nodes

Readme

ByteCave Browser Client

Browser-compatible P2P client library for connecting to the ByteCave decentralized storage network via WebRTC and WebSockets.

Features

  • Fast Node Discovery - Discover storage nodes in 1-2 seconds via relay peer directory protocol
  • WebRTC P2P Connections - Direct browser-to-node connections using WebRTC transport (via libp2p)
  • Circuit Relay Fallback - Relay-based P2P connections for NAT traversal when direct WebRTC fails
  • Pure P2P Architecture - All communication via libp2p protocols (no HTTP endpoints required)
  • FloodSub Announcements - Peer discovery and announcements via pubsub
  • Contract Integration - Optional on-chain node registry verification
  • HashD Protocol - Custom hashd:// URL scheme with caching and helpers
  • React Hooks & Components - Ready-to-use React integration
  • TypeScript - Full type safety

Installation

npm install @gethashd/bytecave-browser
# or
yarn add @gethashd/bytecave-browser

Quick Start

import { ByteCaveClient } from '@gethashd/bytecave-browser';

// Initialize client
const client = new ByteCaveClient({
  appId: 'my-app', // Required: Application identifier
  relayPeers: [
    '/dns4/relay.example.com/tcp/4002/ws/p2p/12D3KooW...'
  ],
  vaultNodeRegistryAddress: '0x...', // VaultNodeRegistry contract
  contentRegistryAddress: '0x...', // ContentRegistry contract
  rpcUrl: 'https://...', // Ethereum RPC URL
  maxPeers: 10,
  connectionTimeout: 30000
});

// Start P2P client
await client.start();

// Store data
const result = await client.store(
  new TextEncoder().encode('Hello ByteCave!'),
  'text/plain'
);
console.log('Stored with CID:', result.cid);

// Retrieve data
const retrieved = await client.retrieve(result.cid);
console.log('Retrieved:', new TextDecoder().decode(retrieved.data));

// Stop client
await client.stop();

Configuration

ByteCaveConfig

interface ByteCaveConfig {
  appId: string;                       // Required: Application identifier for storage authorization
  relayPeers?: string[];               // Relay node multiaddrs for P2P connections
  directNodeAddrs?: string[];          // Direct node multiaddrs (WebRTC without relay)
  vaultNodeRegistryAddress?: string;   // Optional: VaultNodeRegistry contract for node verification
  contentRegistryAddress?: string;     // Optional: ContentRegistry contract
  rpcUrl?: string;                     // Optional: Ethereum RPC (required if using contracts)
  maxPeers?: number;                   // Maximum peer connections (default: 10)
  connectionTimeout?: number;          // Connection timeout in ms (default: 30000)
}

Relay Peer Configuration

Required: At least one relay peer multiaddr for P2P discovery.

relayPeers: [
  // WebSocket multiaddr (required for browsers)
  '/dns4/relay.example.com/tcp/4002/ws/p2p/12D3KooW...',
  // Multiple relays for redundancy (recommended)
  '/dns4/relay2.example.com/tcp/4002/ws/p2p/12D3KooW...'
]

Getting Relay Multiaddrs:

From relay HTTP info endpoint:

curl http://relay.example.com:9090/info

Or from relay node logs:

docker-compose logs relay1 | grep "Listening on"

Important: Use the WebSocket address (contains /ws/) for browser clients.

API Reference

ByteCaveClient

Constructor

new ByteCaveClient(config: ByteCaveConfig)

Methods

start(): Promise<void>

Start the P2P client and connect to relay nodes.

await client.start();

stop(): Promise<void>

Stop the P2P client and disconnect from all peers.

await client.stop();

store(data: Uint8Array, mimeType?: string, signer?: any): Promise<StoreResult>

Store data on the network.

const result = await client.store(data, 'text/plain', signer);
// Returns: { success: true, cid: '...', peerId: '...' }

Note: mimeType is optional and defaults to 'application/octet-stream'. Use standard MIME types like 'text/plain', 'image/jpeg', 'application/json', etc.

retrieve(cid: string): Promise<RetrieveResult>

Retrieve data from the network.

const result = await client.retrieve('bafybei...');
// Returns: { success: true, data: Uint8Array, peerId: '...' }

getPeers(): PeerInfo[]

Get list of connected peers.

const peers = client.getPeers();
// Returns: [{ peerId: '...', publicKey: '...', connected: true, ... }]

getConnectionState(): ConnectionState

Get current connection state.

const state = client.getConnectionState();
// Returns: 'disconnected' | 'connecting' | 'connected' | 'error'

Events

on(event: string, callback: Function): void

Listen for events.

client.on('connectionStateChange', (state) => {
  console.log('Connection state:', state);
});

client.on('peerConnect', (peer) => {
  console.log('Peer connected:', peer.peerId);
});

client.on('peerDisconnect', (peerId) => {
  console.log('Peer disconnected:', peerId);
});

client.on('peerAnnounce', (peer) => {
  console.log('Peer announced:', peer.peerId);
});

off(event: string, callback: Function): void

Remove event listener.

client.off('peerConnect', callback);

HashD Protocol (hashd://)

Custom URL scheme for loading content from ByteCave network. Note: hashd:// URLs are not automatically resolved by browsers - you must use the provided utilities to parse and fetch content.

URL Format

hashd://{cid}
hashd://{cid}?type=image/png
hashd://{cid}?type=image/png&decrypt=true

Core Functions

import { 
  parseHashdUrl, 
  createHashdUrl, 
  fetchHashdContent,
  prefetchHashdContent,
  clearHashdCache,
  getHashdCacheStats,
  revokeHashdUrl
} from '@gethashd/bytecave-browser';

// Parse hashd:// URL into components
const parsed = parseHashdUrl('hashd://bafybei...?type=image/png');
// { protocol: 'hashd:', cid: 'bafybei...', mimeType: 'image/png', raw: '...' }

// Create hashd:// URL from CID
const url = createHashdUrl('bafybei...', { mimeType: 'image/png' });
// 'hashd://bafybei...?type=image/png'

// Fetch content and get blob URL (with automatic caching)
const result = await fetchHashdContent(url, client);
// { data: Uint8Array, blobUrl: 'blob:...', mimeType: 'image/png', cached: false }

// Prefetch and cache content
await prefetchHashdContent('hashd://bafybei...', client);

// Cache management
const stats = getHashdCacheStats(); // { size: 5 }
clearHashdCache(); // Clear all cached blob URLs
revokeHashdUrl('bafybei...'); // Revoke specific blob URL

How It Works

  1. Parse - parseHashdUrl() extracts CID and metadata from URL
  2. Fetch - fetchHashdContent() retrieves data via P2P from ByteCave network
  3. Cache - Blob URLs are automatically cached (1 hour TTL)
  4. Reuse - Subsequent requests for same CID return cached blob URL

React Hooks

For React apps, use hooks instead of manual fetching:

import { useHashdUrl } from '@gethashd/bytecave-browser';

function ImageDisplay({ cid }) {
  const { blobUrl, loading, error } = useHashdUrl(`hashd://${cid}`);
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  return <img src={blobUrl} alt="Image" />;
}

React Integration

Provider Setup (Recommended)

The easiest way to use ByteCave in React is with the ByteCaveProvider:

import { ByteCaveProvider } from '@gethashd/bytecave-browser';

function App() {
  return (
    <ByteCaveProvider
      vaultNodeRegistryAddress={process.env.REACT_APP_VAULT_REGISTRY}
      rpcUrl={process.env.REACT_APP_RPC_URL}
      relayPeers={process.env.REACT_APP_RELAY_PEERS?.split(',')}
    >
      <YourApp />
    </ByteCaveProvider>
  );
}

Then use hooks anywhere in your app:

import { useByteCaveContext, useHashdUrl } from '@gethashd/bytecave-browser';

function ImageGallery() {
  const { store, retrieve, isConnected } = useByteCaveContext();
  
  // Display image from hashd:// URL
  const { blobUrl, loading, error } = useHashdUrl('hashd://abc123...');
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return <img src={blobUrl} alt="Stored image" />;
}

function Uploader() {
  const { store, isConnected } = useByteCaveContext();
  
  const handleUpload = async (file: File) => {
    const data = new Uint8Array(await file.arrayBuffer());
    const result = await store(data, file.type);
    console.log('Uploaded with CID:', result.cid);
  };
  
  return (
    <input 
      type="file" 
      onChange={(e) => handleUpload(e.target.files[0])}
      disabled={!isConnected}
    />
  );
}

Available Hooks

useByteCaveContext() - Access ByteCave client from context

Must be used within ByteCaveProvider. Returns:

{
  connectionState: 'disconnected' | 'connecting' | 'connected' | 'error',
  peers: PeerInfo[],
  isConnected: boolean,
  connect: () => Promise<void>,
  disconnect: () => Promise<void>,
  store: (data: Uint8Array, contentType?: string, signer?: any) => Promise<StoreResult>,
  retrieve: (cid: string) => Promise<RetrieveResult>,
  getNodeHealth: (peerId: string) => Promise<NodeHealth | null>,
  error: string | null
}

useHashdUrl(url) - Convert hashd:// URL to blob URL

function ImageDisplay({ cid }) {
  const { blobUrl, loading, error } = useHashdUrl(`hashd://${cid}`);
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  
  return <img src={blobUrl} alt="Image" />;
}

useHashdImage(url, options) - Image-specific loader

function ProfilePicture({ cid }) {
  const { src, loading, error } = useHashdImage(`hashd://${cid}`, { 
    client,
    placeholder: '/default-avatar.png' 
  });

  if (loading) return <Spinner />;
  if (error) return <ErrorIcon />;
  
  return <img src={src} alt="Profile" className="w-32 h-32 rounded-full" />;
}

useHashdContent(url, options) - Generic content loader

function ContentViewer({ url }) {
  const { blobUrl, loading, error, mimeType } = useHashdContent(url, { client });
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  
  if (mimeType?.startsWith('image/')) {
    return <img src={blobUrl} />;
  } else if (mimeType?.startsWith('video/')) {
    return <video src={blobUrl} controls />;
  }
  
  return <a href={blobUrl} download>Download</a>;
}

useHashdMedia(url, options) - Video/audio loader

Returns: { src, blobUrl, loading, error, cached, refetch }

useHashdBatch(urls, options) - Batch load multiple URLs

function Gallery({ cids }) {
  const urls = cids.map(cid => `hashd://${cid}`);
  const { results, loading, errors } = useHashdBatch(urls, { client });
  
  if (loading) return <Spinner />;
  
  return (
    <div className="grid grid-cols-3 gap-4">
      {Array.from(results.entries()).map(([url, result]) => (
        <img key={url} src={result.blobUrl} alt="" />
      ))}
    </div>
  );
}

Components

Drop-in replacements for standard HTML elements:

import { HashdImage, HashdVideo, HashdAudio } from '@gethashd/bytecave-browser/react';

// Image
<HashdImage 
  src="hashd://abc123..." 
  client={byteCaveClient}
  alt="Profile picture"
  className="w-32 h-32 rounded-full"
  placeholder="/loading.png"
  loadingComponent={<Spinner />}
  errorComponent={<ErrorIcon />}
/>

// Video
<HashdVideo 
  src="hashd://def456..." 
  client={byteCaveClient}
  controls
  className="w-full"
/>

// Audio
<HashdAudio 
  src="hashd://ghi789..." 
  client={byteCaveClient}
  controls
/>

// Render prop pattern
<HashdContent url="hashd://abc123..." client={client}>
  {({ blobUrl, loading, error }) => {
    if (loading) return <Spinner />;
    if (error) return <Error />;
    return <img src={blobUrl} />;
  }}
</HashdContent>

Complete Example with Provider

import { ByteCaveProvider, useByteCaveContext, useHashdUrl } from '@gethashd/bytecave-browser';

// Wrap your app with the provider
function App() {
  return (
    <ByteCaveProvider
      vaultNodeRegistryAddress={process.env.REACT_APP_VAULT_REGISTRY}
      rpcUrl={process.env.REACT_APP_RPC_URL}
      relayPeers={process.env.REACT_APP_RELAY_PEERS?.split(',')}
    >
      <Gallery />
    </ByteCaveProvider>
  );
}

// Use hooks in any component
function Gallery() {
  const { isConnected, store } = useByteCaveContext();
  const { blobUrl, loading, error } = useHashdUrl(
    'hashd://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi'
  );

  const handleUpload = async (file: File) => {
    const data = new Uint8Array(await file.arrayBuffer());
    const result = await store(data, file.type);
    console.log('Uploaded:', result.cid);
  };

  if (!isConnected) return <div>Connecting to ByteCave...</div>;

  return (
    <div>
      <h1>ByteCave Gallery</h1>
      
      {/* Display image */}
      {loading && <div>Loading image...</div>}
      {error && <div>Error: {error}</div>}
      {blobUrl && <img src={blobUrl} alt="Decentralized" className="max-w-md" />}
      
      {/* Upload new image */}
      <input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
    </div>
  );
}

Complete Example without Provider

If you prefer manual client management:

import { ByteCaveClient } from '@gethashd/bytecave-browser';
import { HashdImage } from '@gethashd/bytecave-browser';
import { useState, useEffect } from 'react';

function App() {
  const [client, setClient] = useState(null);

  useEffect(() => {
    const bytecave = new ByteCaveClient({
      relayPeers: process.env.REACT_APP_RELAY_PEERS?.split(','),
      vaultNodeRegistryAddress: process.env.REACT_APP_VAULT_REGISTRY,
      rpcUrl: process.env.REACT_APP_RPC_URL
    });
    
    bytecave.start().then(() => setClient(bytecave));
    
    return () => bytecave.stop();
  }, []);

  if (!client) return <div>Connecting to ByteCave...</div>;

  return (
    <div>
      <h1>ByteCave Gallery</h1>
      <HashdImage 
        src="hashd://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
        client={client}
        alt="Decentralized image"
        className="max-w-md"
      />
    </div>
  );
}

Environment Variables

For React apps, configure relay peers via environment:

# .env
REACT_APP_RELAY_PEERS=/dns4/relay.example.com/tcp/4002/ws/p2p/12D3KooW...

Then in your app:

const relayPeers = process.env.REACT_APP_RELAY_PEERS?.split(',') || [];

const client = new ByteCaveClient({
  contractAddress: process.env.REACT_APP_VAULT_REGISTRY,
  rpcUrl: process.env.REACT_APP_RPC_URL,
  relayPeers
});

How It Works

Fast Discovery via Peer Directory

On startup, the browser queries the relay's peer directory protocol for instant node discovery:

  1. Connect to Relay - Browser connects to relay via WebSocket
  2. Query Peer Directory - Browser dials /bytecave/relay/peers/1.0.0 protocol
  3. Receive Peer List - Relay responds with list of storage nodes and circuit relay addresses
  4. Dial Nodes - Browser dials each node through the relay
  5. Fetch Health Data - Immediate health check via P2P protocol

Discovery time: 1-2 seconds (down from 2+ minutes with gossip-only)

Pure P2P Discovery

  1. Connect to Relay - Browser connects to relay via WebSocket
  2. Announce Presence - Node announces on FloodSub bytecave-announce topic
  3. Discover Peers - DHT and pubsub discover other nodes through relay
  4. Establish Connection - Direct WebRTC or relayed connection
  5. Store/Retrieve - P2P protocols for data operations

Pure P2P Architecture

  • ✅ No HTTP health endpoint calls
  • ✅ No HTTP multiaddr fetching
  • ✅ Pure libp2p protocols (Peer Directory, FloodSub, DHT)
  • ✅ Works entirely over P2P network
  • ✅ Browser connects directly to storage nodes via WebRTC or relay

Protocols

  • Peer Directory (/bytecave/relay/peers/1.0.0) - Fast node discovery
  • Health Protocol (/bytecave/health/1.0.0) - Node health checks
  • WebSocket - Browser to relay connection
  • WebRTC - Direct browser-to-node connections
  • Circuit Relay v2 - NAT traversal
  • FloodSub - Peer announcements
  • DHT (via relay) - Distributed peer routing

Browser Compatibility

  • Chrome/Edge 89+
  • Firefox 87+
  • Safari 15.4+
  • Opera 75+

Requires WebRTC and WebSocket support.

Troubleshooting

No Peers Discovered

Check relay configuration:

console.log('Relay peers:', config.relayPeers);

Verify relay is running:

curl http://relay.example.com:4002

Check browser console:

[ByteCave] Using relay peers: [...]
[ByteCave] ✓ Connected to relay: ...

Connection Failed

  1. Verify relay multiaddr format includes /ws/
  2. Check firewall allows WebSocket connections
  3. Ensure relay peer ID matches running relay
  4. Try connecting to relay directly in browser DevTools

CORS Issues

WebSocket connections don't have CORS restrictions. If you see CORS errors, you may be using HTTP instead of WS.

Development

# Install dependencies
yarn install

# Build
yarn build

# Run tests
yarn test

# Watch mode
yarn dev

TypeScript Support

Full TypeScript definitions included:

import type { 
  ByteCaveConfig,
  PeerInfo,
  StoreResult,
  RetrieveResult,
  ConnectionState,
  HashdUrl,
  FetchOptions,
  FetchResult
} from '@gethashd/bytecave-browser';

License

MIT

Related Packages

  • bytecave-core - Storage node implementation
  • bytecave-relay - Relay node for NAT traversal
  • bytecave-desktop - Desktop application

Support

For issues and questions, please open an issue on GitHub.