@vaultys/peer-sdk
v1.0.1
Published
Decoupled, reusable SDK for peer-to-peer communication with Vaultys
Readme
Vaultys Peer SDK
A decoupled, reusable SDK for peer-to-peer communication built on WebRTC. This SDK provides a clean, interface-based architecture for building peer-to-peer applications with support for messaging, file sharing, audio/video calls, and group communication.
Features
- 🔐 DID-based Identity: Use Decentralized Identifiers for peer identification
- 💬 Messaging: Send text, files, images, and custom data between peers
- 📞 Audio/Video Calls: Built-in support for WebRTC media streams
- 👥 Group Management: Organize peers into groups for easier management
- 💾 Pluggable Storage: Interface-based storage system supporting any backend
- 🚌 Event-Driven Architecture: Decoupled communication via EventBus interface
- 🔄 Auto-Reconnection: Automatic connection recovery with exponential backoff
- 🏥 Health Monitoring: Built-in health checks and connection status tracking
- 📦 TypeScript First: Full TypeScript support with comprehensive type definitions
Installation
npm install @vaultys/peer-sdkor with yarn:
yarn add @vaultys/peer-sdkor with pnpm:
pnpm add @vaultys/peer-sdkQuick Start
import { setupVaultysPeerSDK, PeerService } from '@vaultys/peer-sdk';
// Create a peer service with default configuration
const peerService = setupVaultysPeerSDK({
debug: true,
relay: {
host: 'your-relay-server.com',
port: 443,
path: '/myapp',
secure: true
}
});
// Initialize with your DID
await peerService.initialize('did:example:123456789');
// Add a contact
const peer = await peerService.addContact('did:example:987654321', {
nickname: 'Alice',
accepted: true
});
// Send a message
await peerService.sendMessage('did:example:987654321', 'Hello, Alice!');
// Listen for incoming messages
peerService.on('message-received', (message) => {
console.log(`New message from ${message.from}: ${message.content}`);
});Architecture
The SDK is built with a decoupled architecture using interfaces for maximum flexibility:
Storage Provider Interface
The SDK uses a StorageProvider interface that allows you to plug in any storage backend:
import { StorageProvider, MemoryStorageProvider } from '@vaultys/peer-sdk';
// Use the built-in memory storage (default)
const memoryStorage = new MemoryStorageProvider();
// Or implement your own storage provider
class CustomStorageProvider implements StorageProvider {
async initialize(config?: StorageConfig): Promise<void> {
// Initialize your storage
}
async read(path: string): Promise<Buffer | null> {
// Read data from your storage
}
async write(path: string, data: Buffer | Uint8Array | string): Promise<void> {
// Write data to your storage
}
// ... implement other methods
}
const peerService = setupVaultysPeerSDK({
storageProvider: new CustomStorageProvider()
});Event Bus Interface
The SDK emits events through an EventBus interface, allowing integration with external systems:
import { EventBus, StandardEvents, SimpleEventBus } from '@vaultys/peer-sdk';
// Use the built-in event bus (default)
const eventBus = new SimpleEventBus();
// Listen to SDK events
eventBus.on(StandardEvents.PEER_SERVICE_INITIALIZED, (data) => {
console.log(`Service initialized with DID: ${data.did}`);
// Trigger other services, like a room service
});
eventBus.on(StandardEvents.PEER_CONNECTED, (data) => {
console.log(`Peer connected: ${data.did}`);
});
const peerService = setupVaultysPeerSDK({
eventBus: eventBus
});Core Concepts
DIDs (Decentralized Identifiers)
The SDK uses DIDs as unique identifiers for peers. Each DID is hashed to create a deterministic peer ID for the underlying PeerJS connection.
import { getPeerIdFromDid, isValidDid } from '@vaultys/peer-sdk';
const did = 'did:example:123456789';
if (isValidDid(did)) {
const peerId = getPeerIdFromDid(did); // Generates deterministic peer ID
}Messaging
Send various types of messages between peers:
// Text message
await peerService.sendMessage(did, 'Hello!', 'text');
// File message
const fileData = {
name: 'document.pdf',
size: 1024000,
type: 'application/pdf',
data: arrayBuffer // ArrayBuffer of file contents
};
await peerService.sendMessage(did, 'Sending a file', 'file', fileData);
// Custom JSON message
await peerService.sendMessage(did, JSON.stringify({ custom: 'data' }), 'json');Audio/Video Calls
Make and receive audio/video calls:
// Get user media
const localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
// Make a call
const call = await peerService.makeCall(did, localStream, 'video');
// Handle incoming calls
peerService.on('call-incoming', async (call) => {
const localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: call.type === 'video'
});
await peerService.answerCall(localStream);
});
// End a call
await peerService.endCall();Group Management
Organize contacts into groups:
// Create a group
const group = await peerService.createGroup('Family', 'Family members');
// Add peers to group
await peerService.addPeerToGroup(did, group.id);
// Get peers in a group
const familyPeers = peerService.getPeersByGroup(group.id);
// Update group
await peerService.updateGroup(group.id, {
name: 'Close Family',
metadata: { color: '#ff0000' }
});Health Monitoring
The SDK automatically monitors peer connection health:
peer.on('health-status', (status) => {
console.log(`Peer health: ${status.isHealthy ? 'healthy' : 'unhealthy'}`);
console.log(`RTT: ${status.rtt}ms`);
});
// Get current health status
const health = peer.connectionHealth;API Reference
PeerService
The main service class for managing peer connections.
Methods
initialize(did?: string): Promise<string>- Initialize the service with a DIDdisconnect(): Promise<void>- Disconnect from relayaddContact(did: string, metadata?: any): Promise<VaultysPeer>- Add a new contactremoveContact(did: string): Promise<void>- Remove a contactgetPeers(): VaultysPeer[]- Get all peersgetContacts(): VaultysPeer[]- Get accepted contactsgetPeer(did: string): VaultysPeer | undefined- Get a specific peersendMessage(did: string, content: string, type?: MessageType, fileData?: FileData): Promise<Message>- Send a messagemakeCall(did: string, localStream: MediaStream, type?: CallType): Promise<Call>- Make a callanswerCall(localStream: MediaStream): Promise<void>- Answer incoming callrejectCall(): Promise<void>- Reject incoming callendCall(): Promise<void>- End current call
Events
relay-connected- relay connectedrelay-disconnected- relay disconnectedpeer-status- Peer connection status changedmessage-received- Message received from peermessage-sent- Message sent to peercall-incoming- Incoming callcall-status-changed- Call status changedcontact-online- Contact came onlinecontact-offline- Contact went offlineerror- Error occurred
VaultysPeer
Individual peer connection management.
Properties
did: string- Peer's DIDpeerId: string- Generated peer IDstatus: PeerStatus- Current connection statusmetadata?: PeerMetadata- Peer metadatalastSeen?: Date- Last seen timestamp
Methods
connect(): Promise<void>- Connect to peerdisconnect(): void- Disconnect from peersendMessage(content: string, type?: MessageType, fileData?: FileData): Promise<Message>- Send messagemakeCall(localStream: MediaStream, type?: CallType): Promise<Call>- Make callloadMessages(limit?: number): Promise<Message[]>- Load message historyupdateMetadata(metadata: Partial<PeerMetadata>): void- Update metadata
Advanced Usage
Custom Storage Provider for IndexedDB
import { StorageProvider, StorageConfig, StorageMetadata } from '@vaultys/peer-sdk';
class IndexedDBStorageProvider implements StorageProvider {
private db: IDBDatabase | null = null;
private dbName = 'vaultys-peer-sdk';
async initialize(config?: StorageConfig): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains('files')) {
db.createObjectStore('files', { keyPath: 'path' });
}
};
});
}
async read(path: string): Promise<Buffer | null> {
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['files'], 'readonly');
const store = transaction.objectStore('files');
const request = store.get(path);
request.onsuccess = () => {
const result = request.result;
resolve(result ? Buffer.from(result.data) : null);
};
request.onerror = () => reject(request.error);
});
}
async write(path: string, data: Buffer | Uint8Array | string): Promise<void> {
const buffer = Buffer.isBuffer(data) ? data :
data instanceof Uint8Array ? Buffer.from(data) :
Buffer.from(data, 'utf-8');
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['files'], 'readwrite');
const store = transaction.objectStore('files');
const request = store.put({ path, data: buffer });
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
// Implement other required methods...
}Integration with Room Service
import { EventBus, StandardEvents } from '@vaultys/peer-sdk';
class RoomServiceIntegration {
constructor(private eventBus: EventBus) {
// Listen for peer service initialization
this.eventBus.on(StandardEvents.PEER_SERVICE_INITIALIZED, (data) => {
this.initializeRoomService(data.did);
});
// Listen for room data from peers
this.eventBus.on(StandardEvents.ROOM_DATA_RECEIVED, (data) => {
this.handleRoomData(data);
});
}
private initializeRoomService(did: string) {
// Initialize your room service with the DID
console.log(`Initializing room service for DID: ${did}`);
}
private handleRoomData(data: any) {
// Handle room data received from peers
console.log(`Room data received:`, data);
}
}
// Use with the SDK
const eventBus = new SimpleEventBus();
const roomIntegration = new RoomServiceIntegration(eventBus);
const peerService = setupVaultysPeerSDK({ eventBus });Message Persistence and Caching
// Load message history for a peer
const messages = await peerService.loadMessages(did, 100);
// Group messages by date
const groupedMessages = messages.reduce((groups, message) => {
const date = new Date(message.timestamp).toDateString();
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(message);
return groups;
}, {} as Record<string, Message[]>);
// Implement message search
function searchMessages(messages: Message[], query: string): Message[] {
return messages.filter(msg =>
msg.content.toLowerCase().includes(query.toLowerCase())
);
}Migration from Coupled Implementation
If you're migrating from a tightly coupled implementation, here are the key changes:
Before (Coupled)
// Direct dependency on roomService
roomService.initialize(did, vaultId);
// Direct FileSystem usage
this.fileSystem = new FileSystem();
this.vaultOperations = new VaultOperations();
// Hard-coded paths
const path = PATH.PEERS + '/messages';After (Decoupled)
// Event-based initialization
eventBus.emit(StandardEvents.PEER_SERVICE_INITIALIZED, { did, peerId });
// Storage provider interface
const storageProvider = new CustomStorageProvider();
await storageProvider.write('peers/messages', data);
// Configurable paths
const path = config.storagePath || 'peers/messages';Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
MIT License - see LICENSE file for details.
Support
- 📧 Email: [email protected]
- 💬 Discord: Join our community
- 🐛 Issues: GitHub Issues
- 📖 Docs: Full Documentation
