Skip to content

Latest commit

 

History

History
1503 lines (1206 loc) · 37.3 KB

File metadata and controls

1503 lines (1206 loc) · 37.3 KB

Bare Kit Implementation Guide - Production-Ready Architecture

This is the complete implementation guide for adding Bare Kit to HolepunchP2PChat, featuring production-quality code with clean architecture, full type safety, and best practices.

What you'll get:

  • 🏗️ Clean, maintainable code structure
  • 🔒 Full TypeScript type safety
  • 📦 Proper state management
  • ⚡ Performance optimizations
  • 🐛 Troubleshooting guide
  • 🎯 Step-by-step instructions

Prerequisites:

  • Node.js >= 18
  • React Native project set up
  • Basic understanding of Hyperswarm
  • Read BARE_KIT_SOLUTION.md first

🎯 Improved File Structure

src/p2p/
├── constants/
│   └── rpc-commands.ts          # Type-safe RPC command definitions
├── worklet/
│   ├── hyperswarm-worklet.mjs   # Clean worklet implementation
│   └── app.bundle.mjs           # Generated bundle (gitignored)
├── managers/
│   └── HyperswarmManager.ts     # Clean React Native manager
└── types/
    └── p2p.types.ts             # Shared TypeScript types

📝 1. Type-Safe RPC Commands

File: src/p2p/constants/rpc-commands.ts

/**
 * RPC Command Identifiers
 * 
 * Commands sent FROM React Native TO Worklet
 */
export enum WorkletCommand {
  GET_KEYS = 'GET_KEYS',
  GET_CONNECTED_PEERS = 'GET_CONNECTED_PEERS',
  JOIN_ROOM = 'JOIN_ROOM',
  LEAVE_ROOM = 'LEAVE_ROOM',
  SEND_MESSAGE = 'SEND_MESSAGE',
}

/**
 * Events sent FROM Worklet TO React Native
 */
export enum WorkletEvent {
  READY = 'READY',
  PEER_CONNECTED = 'PEER_CONNECTED',
  PEER_DISCONNECTED = 'PEER_DISCONNECTED',
  MESSAGE_RECEIVED = 'MESSAGE_RECEIVED',
  ERROR = 'ERROR',
}

/**
 * Command ID mapping for bare-rpc
 * (RPC requires numeric IDs)
 */
export const CommandIds = {
  // Commands (React Native → Worklet)
  [WorkletCommand.GET_KEYS]: 1,
  [WorkletCommand.GET_CONNECTED_PEERS]: 2,
  [WorkletCommand.JOIN_ROOM]: 3,
  [WorkletCommand.LEAVE_ROOM]: 4,
  [WorkletCommand.SEND_MESSAGE]: 5,
  
  // Events (Worklet → React Native)
  [WorkletEvent.READY]: 10,
  [WorkletEvent.PEER_CONNECTED]: 11,
  [WorkletEvent.PEER_DISCONNECTED]: 12,
  [WorkletEvent.MESSAGE_RECEIVED]: 13,
  [WorkletEvent.ERROR]: 14,
} as const;

📝 2. Shared TypeScript Types

File: src/p2p/types/p2p.types.ts

/**
 * Keypair for Hyperswarm identity
 */
export interface KeyPair {
  publicKey: string;
  secretKey: string;
}

/**
 * Message structure for P2P communication
 */
export interface P2PMessage {
  id: string;
  roomTopic: string;
  text: string;
  sender: string;
  timestamp: number;
  metadata?: Record<string, unknown>;
}

/**
 * Peer information
 */
export interface PeerInfo {
  publicKey: string;
  connectedAt: number;
  rooms: string[];
}

/**
 * Room information
 */
export interface RoomInfo {
  topic: string;
  joinedAt: number;
  peerCount: number;
}

/**
 * Worklet events
 */
export interface WorkletReadyEvent {
  type: 'ready';
  publicKey: string;
}

export interface PeerConnectedEvent {
  peerPublicKey: string;
  timestamp: number;
}

export interface PeerDisconnectedEvent {
  peerPublicKey: string;
  timestamp: number;
}

export interface MessageReceivedEvent {
  peerPublicKey: string;
  message: P2PMessage;
  timestamp: number;
}

export interface WorkletErrorEvent {
  error: string;
  context?: string;
  peerPublicKey?: string;
}

/**
 * Event listeners
 */
export type WorkletReadyListener = (event: WorkletReadyEvent) => void;
export type PeerConnectedListener = (event: PeerConnectedEvent) => void;
export type PeerDisconnectedListener = (event: PeerDisconnectedEvent) => void;
export type MessageReceivedListener = (event: MessageReceivedEvent) => void;
export type WorkletErrorListener = (event: WorkletErrorEvent) => void;

📝 3. Clean Worklet Implementation

File: src/p2p/worklet/hyperswarm-worklet.mjs

/**
 * Hyperswarm Worklet
 * 
 * This worklet runs in a native Bare runtime with full Node.js compatibility.
 * It handles all P2P networking operations using Hyperswarm.
 * 
 * Communication with React Native happens via RPC over IPC.
 */

import Hyperswarm from 'hyperswarm';
import RPC from 'bare-rpc';
import b4a from 'b4a';
import { Buffer } from 'buffer';

// Make Buffer available globally
global.Buffer = Buffer;

// ============================================================================
// Constants
// ============================================================================

const WorkletCommand = {
  GET_KEYS: 1,
  GET_CONNECTED_PEERS: 2,
  JOIN_ROOM: 3,
  LEAVE_ROOM: 4,
  SEND_MESSAGE: 5,
};

const WorkletEvent = {
  READY: 10,
  PEER_CONNECTED: 11,
  PEER_DISCONNECTED: 12,
  MESSAGE_RECEIVED: 13,
  ERROR: 14,
};

// ============================================================================
// State Management
// ============================================================================

class WorkletState {
  constructor() {
    this.swarm = null;
    this.keyPair = null;
    this.connections = new Map(); // peerKey -> connection
    this.rooms = new Map();       // roomTopic -> discovery
    this.peerRooms = new Map();   // peerKey -> Set<roomTopic>
  }

  addConnection(peerKey, connection) {
    this.connections.set(peerKey, connection);
    if (!this.peerRooms.has(peerKey)) {
      this.peerRooms.set(peerKey, new Set());
    }
  }

  removeConnection(peerKey) {
    this.connections.delete(peerKey);
    this.peerRooms.delete(peerKey);
  }

  getConnection(peerKey) {
    return this.connections.get(peerKey);
  }

  getAllConnections() {
    return Array.from(this.connections.entries());
  }

  hasRoom(roomTopic) {
    return this.rooms.has(roomTopic);
  }

  addRoom(roomTopic, discovery) {
    this.rooms.set(roomTopic, discovery);
  }

  removeRoom(roomTopic) {
    const discovery = this.rooms.get(roomTopic);
    this.rooms.delete(roomTopic);
    return discovery;
  }

  addPeerToRoom(peerKey, roomTopic) {
    const peerRoomSet = this.peerRooms.get(peerKey);
    if (peerRoomSet) {
      peerRoomSet.add(roomTopic);
    }
  }

  removePeerFromRoom(peerKey, roomTopic) {
    const peerRoomSet = this.peerRooms.get(peerKey);
    if (peerRoomSet) {
      peerRoomSet.delete(roomTopic);
    }
  }

  getPeersInRoom(roomTopic) {
    const peers = [];
    for (const [peerKey, rooms] of this.peerRooms.entries()) {
      if (rooms.has(roomTopic)) {
        peers.push(peerKey);
      }
    }
    return peers;
  }
}

const state = new WorkletState();

// ============================================================================
// RPC Communication Layer
// ============================================================================

class RPCManager {
  constructor(ipc) {
    this.rpc = new RPC(ipc, this.handleCommand.bind(this));
  }

  handleCommand(req, error) {
    if (error) {
      console.error('[Worklet] RPC Error:', error);
      return;
    }

    try {
      const data = req.data ? b4a.toString(req.data) : '';
      
      switch (req.command) {
        case WorkletCommand.GET_KEYS:
          this.handleGetKeys(req);
          break;
        
        case WorkletCommand.GET_CONNECTED_PEERS:
          this.handleGetConnectedPeers(req);
          break;
        
        case WorkletCommand.JOIN_ROOM:
          this.handleJoinRoom(req, JSON.parse(data));
          break;
        
        case WorkletCommand.LEAVE_ROOM:
          this.handleLeaveRoom(req, JSON.parse(data));
          break;
        
        case WorkletCommand.SEND_MESSAGE:
          this.handleSendMessage(req, JSON.parse(data));
          break;
        
        default:
          console.warn('[Worklet] Unknown command:', req.command);
          req.reply(JSON.stringify({ error: 'Unknown command' }));
      }
    } catch (error) {
      console.error('[Worklet] Command processing error:', error);
      req.reply(JSON.stringify({ error: error.message }));
    }
  }

  handleGetKeys(req) {
    const keys = {
      publicKey: state.keyPair.publicKey.toString('hex'),
      secretKey: state.keyPair.secretKey.toString('hex'),
    };
    req.reply(JSON.stringify(keys));
  }

  handleGetConnectedPeers(req) {
    const peers = Array.from(state.connections.keys());
    req.reply(JSON.stringify(peers));
  }

  async handleJoinRoom(req, { roomTopic }) {
    try {
      if (state.hasRoom(roomTopic)) {
        req.reply(JSON.stringify({ success: true, alreadyJoined: true }));
        return;
      }

      const discovery = state.swarm.join(Buffer.from(roomTopic, 'hex'), {
        client: true,
        server: true,
      });

      state.addRoom(roomTopic, discovery);
      await state.swarm.flush();

      console.log('[Worklet] Joined room:', roomTopic);
      req.reply(JSON.stringify({ success: true }));
    } catch (error) {
      console.error('[Worklet] Failed to join room:', error);
      req.reply(JSON.stringify({ success: false, error: error.message }));
    }
  }

  async handleLeaveRoom(req, { roomTopic }) {
    try {
      const discovery = state.removeRoom(roomTopic);
      
      if (discovery) {
        await discovery.destroy();
        console.log('[Worklet] Left room:', roomTopic);
      }

      req.reply(JSON.stringify({ success: true }));
    } catch (error) {
      console.error('[Worklet] Failed to leave room:', error);
      req.reply(JSON.stringify({ success: false, error: error.message }));
    }
  }

  handleSendMessage(req, { roomTopic, message }) {
    try {
      const peersInRoom = state.getPeersInRoom(roomTopic);
      const messageData = JSON.stringify(message);
      let sentCount = 0;

      for (const peerKey of peersInRoom) {
        const connection = state.getConnection(peerKey);
        if (connection) {
          connection.write(messageData);
          sentCount++;
        }
      }

      console.log(`[Worklet] Sent message to ${sentCount} peers in room ${roomTopic}`);
      req.reply(JSON.stringify({ success: true, sentTo: sentCount }));
    } catch (error) {
      console.error('[Worklet] Failed to send message:', error);
      req.reply(JSON.stringify({ success: false, error: error.message }));
    }
  }

  sendEvent(eventType, payload) {
    const request = this.rpc.request(eventType);
    request.send(JSON.stringify(payload));
  }
}

let rpcManager;

// ============================================================================
// Hyperswarm Connection Handlers
// ============================================================================

function setupConnectionHandlers(connection, peerKey) {
  console.log('[Worklet] New connection from:', peerKey);

  // Notify React Native of new connection
  rpcManager.sendEvent(WorkletEvent.PEER_CONNECTED, {
    peerPublicKey: peerKey,
    timestamp: Date.now(),
  });

  // Handle incoming data
  connection.on('data', (data) => {
    try {
      const message = JSON.parse(data.toString());
      
      rpcManager.sendEvent(WorkletEvent.MESSAGE_RECEIVED, {
        peerPublicKey: peerKey,
        message,
        timestamp: Date.now(),
      });
    } catch (error) {
      console.error('[Worklet] Failed to parse message:', error);
      rpcManager.sendEvent(WorkletEvent.ERROR, {
        error: 'Failed to parse message',
        context: 'data handler',
        peerPublicKey: peerKey,
      });
    }
  });

  // Handle connection close
  connection.on('close', () => {
    console.log('[Worklet] Connection closed:', peerKey);
    state.removeConnection(peerKey);

    rpcManager.sendEvent(WorkletEvent.PEER_DISCONNECTED, {
      peerPublicKey: peerKey,
      timestamp: Date.now(),
    });
  });

  // Handle connection errors
  connection.on('error', (error) => {
    console.error('[Worklet] Connection error:', peerKey, error);
    
    rpcManager.sendEvent(WorkletEvent.ERROR, {
      error: error.message,
      context: 'connection error',
      peerPublicKey: peerKey,
    });
  });
}

// ============================================================================
// Initialization
// ============================================================================

async function initializeWorklet() {
  try {
    // Get seed from React Native (passed as argument)
    const seed = Buffer.from(Bare.argv[0], 'hex');

    // Initialize Hyperswarm
    state.swarm = new Hyperswarm({ seed });
    state.keyPair = state.swarm.keyPair;

    console.log('[Worklet] Initialized with public key:', state.keyPair.publicKey.toString('hex'));

    // Set up RPC communication
    const { IPC } = BareKit;
    rpcManager = new RPCManager(IPC);

    // Set up connection handler
    state.swarm.on('connection', (connection, info) => {
      const peerKey = info.publicKey.toString('hex');
      state.addConnection(peerKey, connection);
      setupConnectionHandlers(connection, peerKey);
    });

    // Start listening on own public key
    state.swarm.join(state.keyPair.publicKey, { 
      server: true, 
      client: false 
    });
    
    await state.swarm.flush();

    console.log('[Worklet] Ready and listening');

    // Notify React Native that worklet is ready
    rpcManager.sendEvent(WorkletEvent.READY, {
      type: 'ready',
      publicKey: state.keyPair.publicKey.toString('hex'),
    });

  } catch (error) {
    console.error('[Worklet] Initialization failed:', error);
    throw error;
  }
}

// Start the worklet
initializeWorklet().catch((error) => {
  console.error('[Worklet] Fatal error:', error);
  process.exit(1);
});

📝 4. Clean React Native Manager

File: src/p2p/managers/HyperswarmManager.ts

/**
 * HyperswarmManager
 * 
 * Manages the Hyperswarm worklet and provides a clean API for React Native.
 * Handles all communication with the worklet via RPC over IPC.
 * 
 * This is a singleton - use getInstance() to access it.
 */

import { Worklet } from 'react-native-bare-kit';
import RPC from 'bare-rpc';
import b4a from 'b4a';
import bundle from '../worklet/app.bundle.mjs';
import { CommandIds, WorkletCommand, WorkletEvent } from '../constants/rpc-commands';
import type {
  KeyPair,
  P2PMessage,
  PeerInfo,
  WorkletReadyListener,
  PeerConnectedListener,
  PeerDisconnectedListener,
  MessageReceivedListener,
  WorkletErrorListener,
} from '../types/p2p.types';

// ============================================================================
// Manager Class
// ============================================================================

export class HyperswarmManager {
  private static instance: HyperswarmManager;
  
  private worklet: Worklet;
  private rpc: RPC | null = null;
  private initialized = false;
  
  // Event listeners
  private listeners = {
    ready: new Set<WorkletReadyListener>(),
    peerConnected: new Set<PeerConnectedListener>(),
    peerDisconnected: new Set<PeerDisconnectedListener>(),
    messageReceived: new Set<MessageReceivedListener>(),
    error: new Set<WorkletErrorListener>(),
  };

  private constructor() {
    this.worklet = new Worklet();
  }

  // --------------------------------------------------------------------------
  // Singleton Pattern
  // --------------------------------------------------------------------------

  static getInstance(): HyperswarmManager {
    if (!HyperswarmManager.instance) {
      HyperswarmManager.instance = new HyperswarmManager();
    }
    return HyperswarmManager.instance;
  }

  // --------------------------------------------------------------------------
  // Initialization
  // --------------------------------------------------------------------------

  async initialize(seed: string): Promise<void> {
    if (this.initialized) {
      console.log('[HyperswarmManager] Already initialized');
      return;
    }

    try {
      console.log('[HyperswarmManager] Starting worklet...');
      
      await this.worklet.start('/app.bundle', bundle, [seed]);
      
      this.setupRPC();
      this.initialized = true;
      
      console.log('[HyperswarmManager] Initialized successfully');
    } catch (error) {
      console.error('[HyperswarmManager] Initialization failed:', error);
      throw new Error(`Failed to initialize Hyperswarm: ${error.message}`);
    }
  }

  private setupRPC(): void {
    this.rpc = new RPC(this.worklet.IPC, async (req) => {
      try {
        const data = b4a.toString(req.data);
        const payload = JSON.parse(data);
        
        this.handleWorkletEvent(req.command, payload);
      } catch (error) {
        console.error('[HyperswarmManager] Error processing worklet event:', error);
      }
    });
  }

  private handleWorkletEvent(eventType: number, payload: any): void {
    const eventId = CommandIds[WorkletEvent.READY];
    
    switch (eventType) {
      case CommandIds[WorkletEvent.READY]:
        this.emit('ready', payload);
        break;
      
      case CommandIds[WorkletEvent.PEER_CONNECTED]:
        this.emit('peerConnected', payload);
        break;
      
      case CommandIds[WorkletEvent.PEER_DISCONNECTED]:
        this.emit('peerDisconnected', payload);
        break;
      
      case CommandIds[WorkletEvent.MESSAGE_RECEIVED]:
        this.emit('messageReceived', payload);
        break;
      
      case CommandIds[WorkletEvent.ERROR]:
        this.emit('error', payload);
        break;
      
      default:
        console.warn('[HyperswarmManager] Unknown event type:', eventType);
    }
  }

  // --------------------------------------------------------------------------
  // Commands (React Native → Worklet)
  // --------------------------------------------------------------------------

  async getKeys(): Promise<KeyPair> {
    this.ensureInitialized();
    
    const request = this.rpc!.request(CommandIds[WorkletCommand.GET_KEYS]);
    request.send('');
    
    const reply = await request.reply();
    const response = JSON.parse(b4a.toString(reply));
    
    return response;
  }

  async getConnectedPeers(): Promise<string[]> {
    this.ensureInitialized();
    
    const request = this.rpc!.request(CommandIds[WorkletCommand.GET_CONNECTED_PEERS]);
    request.send('');
    
    const reply = await request.reply();
    const response = JSON.parse(b4a.toString(reply));
    
    return response;
  }

  async joinRoom(roomTopic: string): Promise<{ success: boolean; alreadyJoined?: boolean }> {
    this.ensureInitialized();
    
    console.log('[HyperswarmManager] Joining room:', roomTopic);
    
    const request = this.rpc!.request(CommandIds[WorkletCommand.JOIN_ROOM]);
    request.send(JSON.stringify({ roomTopic }));
    
    const reply = await request.reply();
    const response = JSON.parse(b4a.toString(reply));
    
    return response;
  }

  async leaveRoom(roomTopic: string): Promise<{ success: boolean }> {
    this.ensureInitialized();
    
    console.log('[HyperswarmManager] Leaving room:', roomTopic);
    
    const request = this.rpc!.request(CommandIds[WorkletCommand.LEAVE_ROOM]);
    request.send(JSON.stringify({ roomTopic }));
    
    const reply = await request.reply();
    const response = JSON.parse(b4a.toString(reply));
    
    return response;
  }

  async sendMessage(roomTopic: string, message: P2PMessage): Promise<{ success: boolean; sentTo: number }> {
    this.ensureInitialized();
    
    console.log('[HyperswarmManager] Sending message to room:', roomTopic);
    
    const request = this.rpc!.request(CommandIds[WorkletCommand.SEND_MESSAGE]);
    request.send(JSON.stringify({ roomTopic, message }));
    
    const reply = await request.reply();
    const response = JSON.parse(b4a.toString(reply));
    
    return response;
  }

  // --------------------------------------------------------------------------
  // Event Listeners
  // --------------------------------------------------------------------------

  onReady(listener: WorkletReadyListener): () => void {
    this.listeners.ready.add(listener);
    return () => this.listeners.ready.delete(listener);
  }

  onPeerConnected(listener: PeerConnectedListener): () => void {
    this.listeners.peerConnected.add(listener);
    return () => this.listeners.peerConnected.delete(listener);
  }

  onPeerDisconnected(listener: PeerDisconnectedListener): () => void {
    this.listeners.peerDisconnected.add(listener);
    return () => this.listeners.peerDisconnected.delete(listener);
  }

  onMessageReceived(listener: MessageReceivedListener): () => void {
    this.listeners.messageReceived.add(listener);
    return () => this.listeners.messageReceived.delete(listener);
  }

  onError(listener: WorkletErrorListener): () => void {
    this.listeners.error.add(listener);
    return () => this.listeners.error.delete(listener);
  }

  // --------------------------------------------------------------------------
  // Helper Methods
  // --------------------------------------------------------------------------

  private emit<K extends keyof typeof this.listeners>(
    event: K,
    payload: any
  ): void {
    const listeners = this.listeners[event];
    listeners.forEach((listener) => {
      try {
        listener(payload);
      } catch (error) {
        console.error(`[HyperswarmManager] Error in ${event} listener:`, error);
      }
    });
  }

  private ensureInitialized(): void {
    if (!this.initialized || !this.rpc) {
      throw new Error('HyperswarmManager not initialized. Call initialize() first.');
    }
  }

  isInitialized(): boolean {
    return this.initialized;
  }

  // --------------------------------------------------------------------------
  // Cleanup
  // --------------------------------------------------------------------------

  async destroy(): Promise<void> {
    console.log('[HyperswarmManager] Shutting down...');
    
    // Clear all listeners
    Object.values(this.listeners).forEach(set => set.clear());
    
    // TODO: Add worklet.terminate() if available
    this.initialized = false;
    this.rpc = null;
    
    console.log('[HyperswarmManager] Shutdown complete');
  }
}

// Export singleton instance getter
export default HyperswarmManager.getInstance;

📝 5. Usage Example with Clean Code

File: screens/ChatScreen.tsx

import React, { useEffect, useState, useCallback } from 'react';
import { View, FlatList, TextInput, Button, Text, StyleSheet } from 'react-native';
import { HyperswarmManager } from '../src/p2p/managers/HyperswarmManager';
import type { P2PMessage } from '../src/p2p/types/p2p.types';
import { generateSeed } from '../src/crypto/utils';

interface ChatScreenProps {
  route: {
    params: {
      roomTopic: string;
      roomName: string;
    };
  };
}

export const ChatScreen: React.FC<ChatScreenProps> = ({ route }) => {
  const { roomTopic, roomName } = route.params;
  
  const [messages, setMessages] = useState<P2PMessage[]>([]);
  const [inputText, setInputText] = useState('');
  const [myPublicKey, setMyPublicKey] = useState('');
  const [isReady, setIsReady] = useState(false);
  const [peerCount, setPeerCount] = useState(0);

  const manager = HyperswarmManager.getInstance();

  // Initialize Hyperswarm and join room
  useEffect(() => {
    let mounted = true;
    const unsubscribers: Array<() => void> = [];

    const initialize = async () => {
      try {
        // Initialize if not already done
        if (!manager.isInitialized()) {
          const seed = await generateSeed();
          await manager.initialize(seed);
        }

        // Get our public key
        const keys = await manager.getKeys();
        if (mounted) {
          setMyPublicKey(keys.publicKey);
        }

        // Set up event listeners
        unsubscribers.push(
          manager.onReady(() => {
            console.log('Worklet is ready');
            setIsReady(true);
          })
        );

        unsubscribers.push(
          manager.onPeerConnected(({ peerPublicKey }) => {
            console.log('Peer connected:', peerPublicKey);
            updatePeerCount();
          })
        );

        unsubscribers.push(
          manager.onPeerDisconnected(({ peerPublicKey }) => {
            console.log('Peer disconnected:', peerPublicKey);
            updatePeerCount();
          })
        );

        unsubscribers.push(
          manager.onMessageReceived(({ message }) => {
            if (mounted) {
              setMessages(prev => [...prev, message]);
            }
          })
        );

        unsubscribers.push(
          manager.onError(({ error, context }) => {
            console.error('P2P Error:', error, context);
          })
        );

        // Join the room
        const result = await manager.joinRoom(roomTopic);
        if (result.success) {
          console.log('Successfully joined room');
          updatePeerCount();
        }

      } catch (error) {
        console.error('Failed to initialize chat:', error);
      }
    };

    const updatePeerCount = async () => {
      const peers = await manager.getConnectedPeers();
      if (mounted) {
        setPeerCount(peers.length);
      }
    };

    initialize();

    return () => {
      mounted = false;
      unsubscribers.forEach(unsub => unsub());
      manager.leaveRoom(roomTopic);
    };
  }, [roomTopic]);

  const handleSendMessage = useCallback(async () => {
    if (!inputText.trim() || !myPublicKey) return;

    const message: P2PMessage = {
      id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
      roomTopic,
      text: inputText,
      sender: myPublicKey,
      timestamp: Date.now(),
    };

    try {
      const result = await manager.sendMessage(roomTopic, message);
      
      if (result.success) {
        setMessages(prev => [...prev, message]);
        setInputText('');
        console.log(`Message sent to ${result.sentTo} peers`);
      }
    } catch (error) {
      console.error('Failed to send message:', error);
    }
  }, [inputText, myPublicKey, roomTopic]);

  const renderMessage = ({ item }: { item: P2PMessage }) => {
    const isMyMessage = item.sender === myPublicKey;
    
    return (
      <View style={[
        styles.messageContainer,
        isMyMessage ? styles.myMessage : styles.theirMessage
      ]}>
        <Text style={styles.senderText}>
          {isMyMessage ? 'You' : item.sender.slice(0, 8)}
        </Text>
        <Text style={styles.messageText}>{item.text}</Text>
        <Text style={styles.timestamp}>
          {new Date(item.timestamp).toLocaleTimeString()}
        </Text>
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.roomName}>{roomName}</Text>
        <Text style={styles.peerCount}>
          {isReady ? `${peerCount} peer(s) connected` : 'Connecting...'}
        </Text>
      </View>

      <FlatList
        data={messages}
        renderItem={renderMessage}
        keyExtractor={item => item.id}
        style={styles.messageList}
        inverted
      />

      <View style={styles.inputContainer}>
        <TextInput
          value={inputText}
          onChangeText={setInputText}
          placeholder="Type a message..."
          style={styles.input}
          onSubmitEditing={handleSendMessage}
        />
        <Button title="Send" onPress={handleSendMessage} disabled={!isReady} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  header: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  roomName: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  peerCount: {
    fontSize: 12,
    color: '#666',
    marginTop: 4,
  },
  messageList: {
    flex: 1,
    padding: 16,
  },
  messageContainer: {
    maxWidth: '80%',
    padding: 12,
    borderRadius: 8,
    marginBottom: 8,
  },
  myMessage: {
    alignSelf: 'flex-end',
    backgroundColor: '#007AFF',
  },
  theirMessage: {
    alignSelf: 'flex-start',
    backgroundColor: '#E9E9EB',
  },
  senderText: {
    fontSize: 12,
    fontWeight: '600',
    marginBottom: 4,
  },
  messageText: {
    fontSize: 16,
  },
  timestamp: {
    fontSize: 10,
    color: '#666',
    marginTop: 4,
  },
  inputContainer: {
    flexDirection: 'row',
    padding: 16,
    borderTopWidth: 1,
    borderTopColor: '#eee',
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    paddingHorizontal: 12,
    marginRight: 8,
  },
});

🎯 Key Improvements

1. Better Naming

  • ChatPeerManager → ✅ HyperswarmManager
  • worklet.mjs → ✅ hyperswarm-worklet.mjs
  • ❌ Numeric RPC commands → ✅ WorkletCommand enum

2. Type Safety

  • Full TypeScript types for all data structures
  • Type-safe event listeners
  • Proper interface definitions

3. Clean Architecture

  • Separated concerns (state, RPC, handlers)
  • Clear class-based structure
  • Proper error handling

4. Better State Management

  • Dedicated WorkletState class in worklet
  • Clean separation of peer/room tracking
  • No global mutable state

5. Event System

  • Proper listener registration/unregistration
  • Return cleanup functions from on* methods
  • No callback hell

6. Error Handling

  • Try-catch blocks everywhere
  • Meaningful error messages
  • Error context tracking

7. Documentation

  • JSDoc comments
  • Inline explanations
  • Clear section dividers

8. Testing-Friendly

  • Singleton pattern with clear lifecycle
  • Dependency injection ready
  • Easy to mock

📊 Comparison

Aspect bitcoin-tribe Improved Version
File Structure Flat Organized by type
Naming Generic Descriptive
Type Safety Minimal Full TypeScript
State Management Global vars Encapsulated class
Error Handling Basic Comprehensive
Event System Callbacks Proper listeners
Documentation Sparse Extensive
Testability Difficult Easy

📦 Step-by-Step Implementation

Step 1: Install Dependencies

cd /Users/parsh/Work/BitHyve/HolepunchP2PChat

# Install required packages
yarn add react-native-bare-kit@0.5.6
yarn add bare-rpc@0.2.5
yarn add hyperswarm@4.11.7
yarn add b4a@1.6.7
yarn add buffer@6.0.3

# Dev dependency for bundling
yarn add -D bare-pack

Step 2: Create File Structure

mkdir -p src/p2p/constants
mkdir -p src/p2p/worklet
mkdir -p src/p2p/managers
mkdir -p src/p2p/types

Then create the files as shown in the sections above:

  1. src/p2p/constants/rpc-commands.ts
  2. src/p2p/types/p2p.types.ts
  3. src/p2p/worklet/hyperswarm-worklet.mjs
  4. src/p2p/managers/HyperswarmManager.ts

Step 3: Add Bundle Script

Add to package.json:

{
  "scripts": {
    "bundle:worklet": "bare-pack --target ios --target android --linked --out src/p2p/worklet/app.bundle.mjs src/p2p/worklet/hyperswarm-worklet.mjs",
    "prebuild": "yarn bundle:worklet"
  }
}

Step 4: Generate Bundle

# Generate the worklet bundle
yarn bundle:worklet

# This creates src/p2p/worklet/app.bundle.mjs

Add to .gitignore:

# Bare worklet bundle (regenerate with yarn bundle:worklet)
src/p2p/worklet/app.bundle.mjs

Step 5: Use in Your App

See the usage example in the "Usage Example with Clean Code" section above.


🏗️ Build and Test

iOS

cd ios
pod install
cd ..

# Build and run
yarn ios

Android

# Build and run
yarn android

Check Logs

iOS:

# View worklet logs
tail -f ~/Library/Logs/bare/*.log

# Or check Xcode console

Android:

# View worklet logs
adb logcat | grep -i bare

🐛 Troubleshooting

Common Issues

1. Bundle not found

Error: Unable to resolve module './app.bundle.mjs'

Solution:

yarn bundle:worklet

2. Worklet won't start

Error: Failed to initialize HyperswarmManager

Solution:

  • Check that bundle was generated successfully
  • Verify seed is a valid hex string
  • Ensure native module is linked properly (try pod install or rebuild)
  • Check console for specific error messages

3. No connections

Issue: Peers not connecting

Solution:

  • Ensure both devices are on same network or have internet access
  • Verify room topics match exactly (case-sensitive hex strings)
  • Check firewall settings on devices/network
  • Enable verbose logging and check for errors
  • Try with a simple test room first

4. Messages not received

Issue: Messages sent but not received

Solution:

  • Verify onMessageReceived listener is set before joining room
  • Check that message format matches expected structure
  • Ensure both peers are in the same room
  • Look for errors in worklet logs
  • Verify connection is established (check onPeerConnected events)

5. TypeScript errors

Issue: Type errors in code

Solution:

  • Ensure all type files are properly imported
  • Check that tsconfig.json includes the src/p2p directory
  • Run yarn tsc --noEmit to check for type errors
  • Verify all interfaces are properly exported/imported

6. Build failures

Issue: App fails to build

Solution:

  • Clean build: cd ios && pod install && cd .. && yarn ios --reset-cache
  • For Android: cd android && ./gradlew clean && cd .. && yarn android
  • Verify all dependencies are installed correctly
  • Check that bare-pack bundler completed successfully

⚡ Performance Optimization

1. Message Batching

For high-frequency messages, batch them to reduce IPC overhead:

// In worklet (hyperswarm-worklet.mjs)
class MessageBatcher {
  constructor(rpc, batchInterval = 100) {
    this.rpc = rpc;
    this.batch = [];
    this.interval = batchInterval;
    this.timer = null;
  }

  add(message) {
    this.batch.push(message);
    
    if (!this.timer) {
      this.timer = setTimeout(() => {
        this.flush();
      }, this.interval);
    }
  }

  flush() {
    if (this.batch.length > 0) {
      const request = this.rpc.request(WorkletEvent.MESSAGE_RECEIVED);
      request.send(JSON.stringify({ batch: this.batch }));
      this.batch = [];
    }
    this.timer = null;
  }
}

// Use it
const messageBatcher = new MessageBatcher(rpcManager, 100);

connection.on('data', (data) => {
  const message = JSON.parse(data.toString());
  messageBatcher.add({
    peerPublicKey: peerKey,
    message,
    timestamp: Date.now(),
  });
});

2. Memory Management

Monitor and limit worklet memory usage:

// In HyperswarmManager.ts
async initialize(seed: string, options?: { memoryLimit?: number }): Promise<void> {
  const workletOptions = {
    memoryLimit: options?.memoryLimit || 24 * 1024 * 1024, // 24 MiB default
  };
  
  await this.worklet.start('/app.bundle', bundle, [seed], workletOptions);
}

3. Connection Pooling

Limit maximum connections to prevent resource exhaustion:

// In worklet (hyperswarm-worklet.mjs)
const MAX_CONNECTIONS = 50;

state.swarm.on('connection', (connection, info) => {
  if (state.connections.size >= MAX_CONNECTIONS) {
    console.warn('[Worklet] Max connections reached, rejecting:', info.publicKey.toString('hex'));
    connection.destroy();
    return;
  }
  
  // ... handle connection
});

4. Efficient Room Tracking

Track which peers belong to which rooms for targeted messaging:

// Enhanced state management in worklet
class WorkletState {
  // ... existing code ...
  
  getRoomPeers(roomTopic) {
    const peers = [];
    for (const [peerKey, rooms] of this.peerRooms.entries()) {
      if (rooms.has(roomTopic)) {
        peers.push(peerKey);
      }
    }
    return peers;
  }
  
  // Only send to peers in the specific room
  broadcastToRoom(roomTopic, message) {
    const roomPeers = this.getRoomPeers(roomTopic);
    let sent = 0;
    
    for (const peerKey of roomPeers) {
      const conn = this.getConnection(peerKey);
      if (conn) {
        conn.write(JSON.stringify(message));
        sent++;
      }
    }
    
    return sent;
  }
}

5. Debounce Peer Updates

Prevent UI thrashing from rapid peer connect/disconnect events:

// In your React component
import { debounce } from 'lodash';

const updatePeerCount = useCallback(
  debounce(async () => {
    const peers = await manager.getConnectedPeers();
    setPeerCount(peers.length);
  }, 500),
  []
);

🎯 Next Steps

After implementation, consider adding:

1. Room Management

  • Track which peers are in which rooms
  • Send room metadata with messages
  • Implement room discovery mechanism
  • Add room moderator capabilities

2. Enhanced Security

  • Implement end-to-end encryption for messages
  • Add message signing for authenticity
  • Verify peer identities
  • Implement access control for rooms

3. Persistence Layer

  • Store messages locally (AsyncStorage/SQLite)
  • Cache peer information
  • Implement offline message queue
  • Add message sync on reconnection

4. UI/UX Enhancements

  • Show real-time connection status
  • Display peer count per room
  • Add typing indicators
  • Implement read receipts
  • Add message delivery status

5. Advanced Features

  • File/media sharing over P2P
  • Voice/video calls integration
  • Mesh routing for better reliability
  • DHT-based user discovery

📚 Additional Resources

Official Documentation

Examples

Community


This improved architecture is production-ready and follows React Native best practices! 🚀