@asitni/sip-simple
v1.0.0
Published
TypeScript SIP client library with WebRTC support
Maintainers
Readme
TypeScript SIP Client Library
A modern, fully-typed SIP client library built with TypeScript for browser-based VoIP applications. Features WebRTC integration, persistent call history, and comprehensive device management.
✨ Features
- 🎯 Full TypeScript Support - Complete type safety with comprehensive type definitions
- 📞 Complete SIP Functionality - Make, receive, hold, transfer calls with full SIP protocol support
- 🎵 Advanced Audio Management - Device selection, mute/unmute, DTMF, volume control
- 📱 WebRTC Integration - High-quality peer-to-peer audio communication
- 💾 Persistent Storage - Call history with localStorage and memory fallback
- 🔄 Auto-Reconnection - Smart reconnection with exponential backoff
- 🎛️ Event-Driven Architecture - Comprehensive event system for real-time updates
- 📊 Call Statistics - Detailed audio quality and connection analytics
- 🏗️ Modern Build System - Multiple formats (ESM, CJS, UMD) with tree-shaking support
- 🧪 Testing Ready - Built-in test utilities and comprehensive coverage
📦 Installation
npm install @your-org/sip-client sip.js🚀 Quick Start
import { SIPModule, SIPConfig } from '@your-org/sip-client';
// Create SIP client instance
const sipClient = new SIPModule();
// Configure connection
const config: SIPConfig = {
sipUri: 'sip:[email protected]',
wsUri: 'wss://your-sip-server.com:8089/ws',
password: 'your-password',
displayName: 'Your Display Name'
};
// Setup event listeners
sipClient.on('connected', () => {
console.log('✅ Connected to SIP server');
});
sipClient.on('registered', () => {
console.log('📝 Successfully registered');
});
sipClient.on('incomingCall', (callData) => {
console.log(`📞 Incoming call from: ${callData.number}`);
// Answer the call
sipClient.answer();
// Or reject it
// sipClient.reject();
});
sipClient.on('callAccepted', () => {
console.log('🎉 Call established');
});
sipClient.on('callEnded', (data) => {
console.log(`📴 Call ended: ${data.reason}`);
});
// Connect and start calling
async function start() {
try {
const result = await sipClient.connect(config);
if (result.success) {
console.log('🔌 Connected successfully');
// Make a call
await sipClient.makeCall('1234567890');
}
} catch (error) {
console.error('❌ Connection failed:', error);
}
}
start();🔧 Configuration
Basic Configuration
interface SIPConfig {
sipUri: string; // SIP URI (sip:[email protected])
wsUri: string; // WebSocket URI (wss://server:port/path)
password: string; // Authentication password
displayName?: string; // Display name for calls
iceServers?: RTCIceServer[]; // STUN/TURN servers
connection?: ConnectionConfig;
registrationExpires?: number; // Registration expiration (seconds)
}Advanced Connection Settings
interface ConnectionConfig {
connectionTimeout?: number; // Connection timeout (ms) - default: 10000
maxReconnectionAttempts?: number; // Max reconnection attempts - default: 5
reconnectionTimeout?: number; // Reconnection delay (ms) - default: 4000
registrationExpires?: number; // Registration expiration - default: 300
}Example with Advanced Settings
const config: SIPConfig = {
sipUri: 'sip:[email protected]',
wsUri: 'wss://sip.company.com:8089/ws',
password: 'secure-password',
displayName: 'Alice Johnson',
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your-turn-server.com:3478',
username: 'turnuser',
credential: 'turnpass'
}
],
connection: {
connectionTimeout: 15000,
maxReconnectionAttempts: 10,
reconnectionTimeout: 2000,
registrationExpires: 600
}
};📞 Core API
Connection Management
// Connect to SIP server
const result = await sipClient.connect(config);
// Disconnect
await sipClient.disconnect();
// Reconnect with last configuration
await sipClient.reconnect();
// Check connection status
const isConnected = sipClient.isConnected(); // boolean
const state = sipClient.getState(); // ModuleStateCall Management
// Make outgoing call
const result = await sipClient.makeCall('1234567890');
// With custom domain
await sipClient.makeCall('1234567890', 'custom-domain.com');
// Handle incoming calls
sipClient.on('incomingCall', async (callData) => {
console.log(`Call from: ${callData.number}`);
// Answer
await sipClient.answer();
// Or reject
// await sipClient.reject();
});
// Call control
await sipClient.hangup(); // End call
await sipClient.hold(); // Put on hold
await sipClient.unhold(); // Remove from hold
await sipClient.transfer('5551234'); // Transfer call
// Check call status
const hasActiveCall = sipClient.hasActiveCall();
const callState = sipClient.getCallState();Audio Control
// Microphone control
const muteResult = sipClient.toggleMute();
console.log(`Muted: ${muteResult.muted}`);
// Volume control (0.0 - 1.0)
sipClient.setVolume(0.8);
const currentVolume = sipClient.getVolume();
// DTMF tones
sipClient.sendDTMF('1'); // Send single digit
sipClient.sendDTMF('*', 200, 100); // Custom duration and gap
// Check audio status
const isMuted = sipClient.isMuted();Device Management
// Get available devices
const devices = await sipClient.getAvailableDevices();
console.log('Microphones:', devices.microphones);
console.log('Speakers:', devices.speakers);
// Set devices
await sipClient.setMicrophone(deviceId); // null for default
await sipClient.setSpeaker(deviceId);
// Get current devices
const current = sipClient.getCurrentDevices();
// Test devices
const micTest = await sipClient.testMicrophone(deviceId);
if (micTest.success) {
console.log('Audio level:', micTest.getLevel?.()); // 0-100
micTest.stop?.(); // Stop test
}
await sipClient.testSpeaker(deviceId); // Plays test tone
// Check speaker selection support
const supportsSelection = sipClient.isSpeakerSelectionSupported();📊 Call History & Statistics
// Get call history
const history = sipClient.getCallHistory(10); // Last 10 calls
history.forEach(call => {
console.log(`${call.direction} call ${call.number} - ${call.duration}ms`);
});
// Get call statistics
const stats = sipClient.getCallStatistics();
console.log(`Total calls: ${stats.totalCalls}`);
console.log(`Answered: ${stats.answeredCalls}`);
console.log(`Average duration: ${stats.averageDuration}ms`);
// Clear history
sipClient.clearCallHistory();
// Get real-time call stats
const callStats = await sipClient.getCallStats();
if (callStats) {
console.log(`Audio quality - Jitter: ${callStats.jitter}ms`);
console.log(`Packets lost: ${callStats.packetsLost}`);
}🎛️ Event System
Connection Events
sipClient.on('connected', () => {
// Connected to SIP server transport
});
sipClient.on('registered', () => {
// Successfully registered with SIP server
});
sipClient.on('disconnected', () => {
// Disconnected from SIP server
});
sipClient.on('registrationFailed', (error: string) => {
// Registration failed
});Call Events
sipClient.on('callStarted', (callData: CallData) => {
// Outgoing call initiated
});
sipClient.on('incomingCall', (callData: CallData) => {
// Incoming call received
});
sipClient.on('callProgress', () => {
// Call is ringing/progressing
});
sipClient.on('callAccepted', () => {
// Call was answered/established
});
sipClient.on('callEnded', (data: { reason: CallEndReason }) => {
// Call ended - reasons: 'remote_hangup', 'local_hangup', 'cancelled', 'rejected', 'terminated'
});
sipClient.on('callAnswered', () => {
// You answered an incoming call
});
sipClient.on('callRejected', () => {
// You rejected an incoming call
});Audio & Control Events
sipClient.on('muteToggled', (data: { muted: boolean }) => {
// Microphone mute status changed
});
sipClient.on('callHeld', () => {
// Call put on hold
});
sipClient.on('callUnheld', () => {
// Call removed from hold
});
sipClient.on('dtmfSent', (data: { digit: string }) => {
// DTMF tone sent
});Transfer Events
sipClient.on('callRefer', (data: { referTo?: string; referredBy?: string }) => {
// Call transfer initiated
});
sipClient.on('transferAccepted', () => {
// Transfer accepted
});
sipClient.on('transferRejected', () => {
// Transfer rejected
});System Events
sipClient.on('error', (error: string) => {
// Error occurred
});
sipClient.on('stateChanged', (data: { oldState: ModuleState; newState: ModuleState }) => {
// Module state changed
});🏗️ Advanced Usage
Custom Event Handling
import { SIPModule, CallEndReason } from '@your-org/sip-client';
class CustomSIPHandler {
private sipClient: SIPModule;
constructor() {
this.sipClient = new SIPModule();
this.setupEvents();
}
private setupEvents() {
// Auto-answer incoming calls
this.sipClient.on('incomingCall', (callData) => {
if (this.shouldAutoAnswer(callData.number)) {
this.sipClient.answer();
}
});
// Auto-redial on call failure
this.sipClient.on('callEnded', (data) => {
if (data.reason === CallEndReason.REJECTED && this.shouldRetry()) {
setTimeout(() => this.retryCall(), 5000);
}
});
}
private shouldAutoAnswer(number: string): boolean {
// Your logic here
return this.whitelist.includes(number);
}
}Integration with React
import { useEffect, useState } from 'react';
import { SIPModule, SIPConfig, CallData } from '@your-org/sip-client';
export function useSIPClient(config: SIPConfig) {
const [sipClient] = useState(() => new SIPModule());
const [isConnected, setIsConnected] = useState(false);
const [currentCall, setCurrentCall] = useState<CallData | null>(null);
useEffect(() => {
sipClient.on('connected', () => setIsConnected(true));
sipClient.on('disconnected', () => setIsConnected(false));
sipClient.on('incomingCall', setCurrentCall);
sipClient.on('callEnded', () => setCurrentCall(null));
sipClient.connect(config);
return () => sipClient.destroy();
}, []);
return { sipClient, isConnected, currentCall };
}🎯 TypeScript Types
Core Types
interface CallData {
sessionId: string;
number: string;
direction: 'incoming' | 'outgoing';
startTime: Date;
establishedTime?: Date;
endTime?: Date;
state?: string;
}
interface OperationResult {
success: boolean;
error?: string;
sessionId?: string;
deviceId?: string;
}
interface AudioStats {
bytesReceived: number;
bytesSent: number;
packetsLost: number;
jitter: number;
rtt: number;
audioLevel: number;
}Enums
enum CallEndReason {
REMOTE_HANGUP = 'remote_hangup',
LOCAL_HANGUP = 'local_hangup',
CANCELLED = 'cancelled',
REJECTED = 'rejected',
TERMINATED = 'terminated'
}
enum ModuleState {
IDLE = 'idle',
CONNECTING = 'connecting',
CONNECTED = 'connected',
CALLING = 'calling',
IN_CALL = 'in_call',
DISCONNECTING = 'disconnecting',
ERROR = 'error'
}🏪 Storage & Persistence
The library automatically manages call history using localStorage with graceful fallback:
// Check storage availability
const hasStorage = sipClient.isStorageAvailable();
// History is automatically persisted
const history = sipClient.getCallHistory();
// Clear if needed
sipClient.clearCallHistory();🔧 Error Handling
// Method-level error handling
const result = await sipClient.makeCall('1234567890');
if (!result.success) {
console.error('Call failed:', result.error);
}
// Global error handling
sipClient.on('error', (error) => {
console.error('SIP Error:', error);
// Handle connection issues, call failures, etc.
});🌐 Browser Support
- Chrome/Chromium 60+
- Firefox 60+
- Safari 12+
- Edge 79+
Requirements:
- WebRTC API support
- getUserMedia API support
- WebSocket support
- ES2020+ support
🚀 Performance
- Tree-shakeable - Only import what you need
- Memory efficient - Automatic cleanup and resource management
- Optimized builds - Separate ESM, CJS, and UMD bundles
- Lazy loading - sip.js loaded on demand
🧪 Testing
# Run tests
npm test
# Type checking
npm run type-check
# Build verification
npm run build🔗 Integration Examples
Express.js Backend Integration
// server.js
import express from 'express';
import { SIPModule } from '@your-org/sip-client';
const app = express();
const sipClient = new SIPModule();
app.post('/api/call', async (req, res) => {
const { number } = req.body;
const result = await sipClient.makeCall(number);
res.json(result);
});WebSocket Notifications
// Send call events to WebSocket clients
sipClient.on('incomingCall', (callData) => {
websocketServer.broadcast({
type: 'incoming_call',
data: callData
});
});📋 Migration Guide
From v0.x to v1.x
// Old way (v0.x)
import SIPClient from '@your-org/sip-client';
const client = new SIPClient();
// New way (v1.x)
import { SIPModule } from '@your-org/sip-client';
const client = new SIPModule();🤝 Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Run tests:
npm test - Commit:
git commit -m 'Add amazing feature' - Push:
git push origin feature/amazing-feature - Submit a pull request
📄 License
MIT License - see LICENSE file for details.
📞 Support
- GitHub Issues: Report bugs
- Discussions: Community support
- Documentation: Full API docs
🙏 Acknowledgments
- Built on top of sip.js
- WebRTC implementation inspired by modern standards
- TypeScript best practices from the community
Made with ❤️ for the WebRTC community
