UNPKG

aethercall

Version:

A scalable WebRTC video calling API built with Node.js and OpenVidu

1,119 lines (890 loc) 30.4 kB
# AetherCall [![npm version](https://badge.fury.io/js/aethercall.svg)](https://badge.fury.io/js/aethercall) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Node.js CI](https://github.com/RayenMiri/AetherCall/workflows/Node.js%20CI/badge.svg)](https://github.com/RayenMiri/AetherCall/actions) AetherCall is a production-ready, open-source video calling API built on top of OpenVidu. It provides a robust HTTP REST API for integrating real-time video communication, audio calling, screen sharing, and recording capabilities into any application. ## Features - **Real-time Video & Audio**: High-quality WebRTC-based communication - **Screen Sharing**: Share entire screen or specific applications with participants - **Session Recording**: Record video sessions with configurable layouts and formats - **Room-based Access**: Simple room code system for easy participant joining - **JWT Authentication**: Secure token-based authentication with role management - **HTTP REST API**: Clean, documented API endpoints for all operations - **Database Flexibility**: Support for PostgreSQL, MongoDB, filesystem, or in-memory storage - **Production Ready**: Built-in rate limiting, CORS, security headers, and error handling - **Comprehensive Testing**: 45+ automated tests covering all functionality ## Architecture ``` AetherCall/ ├── index.js # Main application entry point ├── src/ │ ├── core/ # Core business logic │ │ ├── auth/ # JWT token management │ │ ├── openvidu/ # OpenVidu API wrapper │ │ └── storage/ # Database abstraction layer │ │ │ ├── interfaces/ │ │ └── http/ # HTTP REST API │ │ ├── routes/ # API route handlers │ │ └── server.js # Express server setup │ │ │ └── utils/ # Shared utilities │ ├── config.js # Configuration management │ └── logger.js # Structured logging │ ├── tests/ # Test suite (Jest) ├── docs/ # API documentation └── package.json # Project dependencies ``` ## Installation ### Prerequisites - **Node.js** 16.x or higher - **npm** or **yarn** - **OpenVidu Server** (Docker recommended) ### Install via npm ```bash npm install aethercall ``` ### Install from Source ```bash git clone https://github.com/RayenMiri/AetherCall.git cd AetherCall npm install ``` ## Quick Start ### 1. Start OpenVidu Server AetherCall requires an OpenVidu server. The easiest way is using Docker: ```bash # Pull and run OpenVidu development server docker run -p 4443:4443 --rm \ -e OPENVIDU_SECRET=MY_SECRET \ openvidu/openvidu-dev:2.29.0 ``` ### 2. Configure Environment Create a `.env` file in your project root: ```env # OpenVidu Configuration OPENVIDU_URL=http://localhost:4443 OPENVIDU_SECRET=MY_SECRET # Server Configuration PORT=3000 HOST=localhost JWT_SECRET=your-super-secret-jwt-key-change-this-in-production # Database (optional - defaults to memory) DB_TYPE=memory # Logging LOG_LEVEL=info ``` ### 3. Start the API Server ```bash # Using npm npm start # Using yarn yarn start # Development mode with auto-reload npm run dev # Or using the setup helper npm run setup ``` ### 4. Using as a Module See `examples/usage-example.js` for detailed examples of how to integrate AetherCall into your existing Node.js applications: ```bash # Run different usage examples node examples/usage-example.js simple # Simple usage with defaults node examples/usage-example.js advanced # Advanced configuration node examples/usage-example.js express # Integration with Express app node examples/usage-example.js production # Production configuration ``` The API will be available at `http://localhost:3000` ### 5. Verify Installation Check if the API is running: ```bash curl http://localhost:3000/health ``` Expected response: ```json { "status": "healthy", "timestamp": "2025-07-27T10:00:00.000Z", "version": "1.0.0" } ``` ## API Usage ### Base URL ``` http://localhost:3000/api ``` ### Authentication All API requests (except health check) require a JWT token: ```javascript // Get API token const response = await fetch('/api/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientId: 'your-application-id', expiresIn: '1h' // optional }) }); const { data } = await response.json(); const apiToken = data.accessToken; ``` ### Core Endpoints #### Sessions Management ```javascript // Create a video session const session = await fetch('/api/sessions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ mediaMode: 'ROUTED', recordingMode: 'MANUAL', metadata: JSON.stringify({ roomName: 'My Meeting' }) }) }); // Get session info const sessionInfo = await fetch(`/api/sessions/${sessionId}`, { headers: { 'Authorization': `Bearer ${apiToken}` } }); // Close session await fetch(`/api/sessions/${sessionId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${apiToken}` } }); ``` #### Connection Management ```javascript // Create connection token for participant const connection = await fetch('/api/connections', { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'session-id', role: 'PUBLISHER', // SUBSCRIBER, PUBLISHER, MODERATOR data: JSON.stringify({ userId: 'user123', displayName: 'John Doe' }) }) }); // Join room with room code (simplified API) const roomConnection = await fetch('/api/connections/join-room', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomCode: 'ABC123', userId: 'user123', displayName: 'John Doe', role: 'PUBLISHER' }) }); ``` #### Recording Management ```javascript // Start recording const recording = await fetch('/api/recordings/start', { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'session-id', name: 'my-recording', outputMode: 'COMPOSED', recordingLayout: 'BEST_FIT' }) }); // Stop recording await fetch(`/api/recordings/stop/${recordingId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}` } }); // Get recording info const recordingInfo = await fetch(`/api/recordings/${recordingId}`, { headers: { 'Authorization': `Bearer ${apiToken}` } }); ``` #### Room Management (Simplified API) ```javascript // Create room with simple code const room = await fetch('/api/auth/room', { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ roomName: 'Daily Standup', maxParticipants: 10, roomCode: 'STANDUP123' // optional, will be generated if not provided }) }); ``` ## Frontend Integration ### Using with OpenVidu Browser SDK #### React Example ```jsx import React, { useState, useEffect, useRef } from 'react'; import { OpenVidu } from 'openvidu-browser'; function VideoRoom({ roomCode, userDisplayName }) { const [session, setSession] = useState(null); const [publisher, setPublisher] = useState(null); const [subscribers, setSubscribers] = useState([]); const publisherRef = useRef(null); useEffect(() => { joinRoom(); return () => leaveRoom(); }, []); const joinRoom = async () => { try { // Get connection token from AetherCall API const response = await fetch('/api/connections/join-room', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomCode: roomCode, userId: `user-${Date.now()}`, displayName: userDisplayName, role: 'PUBLISHER' }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const { data } = await response.json(); // Initialize OpenVidu session const OV = new OpenVidu(); const session = OV.initSession(); // Event handlers session.on('streamCreated', (event) => { const subscriber = session.subscribe(event.stream, undefined); setSubscribers(prev => [...prev, subscriber]); }); session.on('streamDestroyed', (event) => { setSubscribers(prev => prev.filter(sub => sub.stream !== event.stream) ); }); // Connect to session await session.connect(data.token, { clientData: data.userId }); // Create and publish local stream const publisher = await OV.initPublisherAsync(undefined, { audioSource: undefined, videoSource: undefined, publishAudio: true, publishVideo: true, resolution: '640x480', frameRate: 30, insertMode: 'APPEND', mirror: false }); await session.publish(publisher); setSession(session); setPublisher(publisher); } catch (error) { console.error('Error joining room:', error); } }; const leaveRoom = () => { if (session) { session.disconnect(); } }; const toggleVideo = () => { if (publisher) { publisher.publishVideo(!publisher.stream.videoActive); } }; const toggleAudio = () => { if (publisher) { publisher.publishAudio(!publisher.stream.audioActive); } }; return ( <div className="video-room"> <div className="controls"> <button onClick={toggleVideo}> {publisher?.stream?.videoActive ? 'Turn Off Video' : 'Turn On Video'} </button> <button onClick={toggleAudio}> {publisher?.stream?.audioActive ? 'Mute' : 'Unmute'} </button> <button onClick={leaveRoom}>Leave Room</button> </div> <div className="video-container"> <div ref={publisherRef} className="publisher-video" /> <div className="subscribers"> {subscribers.map((subscriber, index) => ( <div key={index} ref={(ref) => { if (ref && subscriber.videos[0]) { subscriber.addVideoElement(ref); } }} className="subscriber-video" /> ))} </div> </div> </div> ); } export default VideoRoom; ``` #### Vanilla JavaScript Example ```html <!DOCTYPE html> <html> <head> <title>AetherCall Video Room</title> <script src="https://github.com/OpenVidu/openvidu/releases/download/v2.29.0/openvidu-browser-2.29.0.min.js"></script> </head> <body> <div id="video-container"> <div id="publisher"></div> <div id="subscribers"></div> </div> <div id="controls"> <button onclick="toggleVideo()">Toggle Video</button> <button onclick="toggleAudio()">Toggle Audio</button> <button onclick="leaveRoom()">Leave Room</button> </div> <script> let session; let publisher; async function joinRoom(roomCode, displayName) { try { // Get token from AetherCall const response = await fetch('/api/connections/join-room', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomCode: roomCode, userId: `user-${Date.now()}`, displayName: displayName, role: 'PUBLISHER' }) }); const { data } = await response.json(); // Create OpenVidu session const OV = new OpenVidu(); session = OV.initSession(); // Handle new streams session.on('streamCreated', (event) => { const subscriber = session.subscribe(event.stream, 'subscribers'); }); // Connect and publish await session.connect(data.token); publisher = await OV.initPublisherAsync(undefined, { publishAudio: true, publishVideo: true }); await session.publish(publisher); publisher.addVideoElement('publisher'); } catch (error) { console.error('Error:', error); } } function toggleVideo() { if (publisher) { publisher.publishVideo(!publisher.stream.videoActive); } } function toggleAudio() { if (publisher) { publisher.publishAudio(!publisher.stream.audioActive); } } function leaveRoom() { if (session) { session.disconnect(); } } // Join room on page load joinRoom('DEMO123', 'Demo User'); </script> </body> </html> ``` ## Backend Integration ### Using AetherCall as a Service #### Express.js Integration ```javascript const express = require('express'); const axios = require('axios'); const app = express(); const AETHERCALL_API_URL = 'http://localhost:3000/api'; // Get API token (you should cache this) async function getAetherCallToken() { const response = await axios.post(`${AETHERCALL_API_URL}/auth/token`, { clientId: 'your-app-id' }); return response.data.data.accessToken; } // Create meeting endpoint app.post('/api/meetings', async (req, res) => { try { const token = await getAetherCallToken(); // Create room in AetherCall const roomResponse = await axios.post(`${AETHERCALL_API_URL}/auth/room`, { roomName: req.body.title, maxParticipants: req.body.maxParticipants || 10 }, { headers: { Authorization: `Bearer ${token}` } }); const { data } = roomResponse.data; res.json({ meetingId: data.sessionId, roomCode: data.roomCode, joinUrl: `${req.protocol}://${req.get('host')}/join/${data.roomCode}` }); } catch (error) { console.error('Error creating meeting:', error); res.status(500).json({ error: 'Failed to create meeting' }); } }); // Join meeting endpoint app.post('/api/meetings/:roomCode/join', async (req, res) => { try { const { roomCode } = req.params; const { userId, displayName, role = 'PUBLISHER' } = req.body; const connectionResponse = await axios.post(`${AETHERCALL_API_URL}/connections/join-room`, { roomCode, userId, displayName, role }); res.json(connectionResponse.data); } catch (error) { console.error('Error joining meeting:', error); res.status(500).json({ error: 'Failed to join meeting' }); } }); ``` #### Recording Management ```javascript // Start recording for a session app.post('/api/meetings/:sessionId/recording/start', async (req, res) => { try { const token = await getAetherCallToken(); const { sessionId } = req.params; const recording = await axios.post(`${AETHERCALL_API_URL}/recordings/start`, { sessionId, name: `meeting-${sessionId}-${Date.now()}`, outputMode: 'COMPOSED', recordingLayout: 'BEST_FIT' }, { headers: { Authorization: `Bearer ${token}` } }); res.json({ recordingId: recording.data.data.id, status: 'started' }); } catch (error) { console.error('Error starting recording:', error); res.status(500).json({ error: 'Failed to start recording' }); } }); // Stop recording app.post('/api/recordings/:recordingId/stop', async (req, res) => { try { const token = await getAetherCallToken(); const { recordingId } = req.params; const recording = await axios.post(`${AETHERCALL_API_URL}/recordings/stop/${recordingId}`, {}, { headers: { Authorization: `Bearer ${token}` } }); res.json({ recordingId, duration: recording.data.data.duration, downloadUrl: recording.data.data.url, status: 'completed' }); } catch (error) { console.error('Error stopping recording:', error); res.status(500).json({ error: 'Failed to stop recording' }); } }); ``` ### Using AetherCall as a Library ```javascript const AetherCall = require('aethercall'); // Initialize AetherCall instance const aetherCall = new AetherCall({ openviduUrl: process.env.OPENVIDU_URL, openviduSecret: process.env.OPENVIDU_SECRET, jwtSecret: process.env.JWT_SECRET, storage: { type: 'postgresql', connectionString: process.env.DATABASE_URL } }); // Start the server app.listen(3000, async () => { await aetherCall.start(); console.log('AetherCall server running on port 3000'); }); ``` ## Configuration ### Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `OPENVIDU_URL` | OpenVidu server URL | `http://localhost:4443` | | `OPENVIDU_SECRET` | OpenVidu server secret | Required | | `PORT` | HTTP server port | `3000` | | `JWT_SECRET` | JWT signing secret | Required | | `DB_TYPE` | Database type | `memory` | | `LOG_LEVEL` | Logging level | `info` | ### Database Support ```env # PostgreSQL DB_TYPE=postgres DB_CONNECTION_STRING=postgresql://user:pass@localhost:5432/aethercall # MongoDB DB_TYPE=mongodb DB_CONNECTION_STRING=mongodb://localhost:27017/aethercall # File System DB_TYPE=filesystem DATA_PATH=./data # Memory (Development) DB_TYPE=memory ``` ## Deployment ### Production Deployment #### Environment Variables for Production ```env # OpenVidu Configuration OPENVIDU_URL=https://your-openvidu-server.com:4443 OPENVIDU_SECRET=your-production-secret # Server Configuration NODE_ENV=production PORT=3000 HOST=0.0.0.0 JWT_SECRET=your-super-secure-jwt-secret-change-this # Database DB_TYPE=postgresql DB_CONNECTION_STRING=postgresql://user:password@host:5432/aethercall # Security CORS_ORIGIN=https://yourdomain.com RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 # Logging LOG_LEVEL=warn ``` #### Docker Deployment **Dockerfile** ```dockerfile FROM node:18-alpine WORKDIR /app # Copy package files COPY package*.json ./ RUN npm ci --only=production # Copy source code COPY . . # Create non-root user RUN addgroup -g 1001 -S nodejs RUN adduser -S aethercall -u 1001 USER aethercall EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" CMD ["npm", "start"] ``` **Docker Compose with PostgreSQL** ```yaml version: '3.8' services: aethercall: build: . ports: - "3000:3000" environment: - NODE_ENV=production - OPENVIDU_URL=http://openvidu:4443 - OPENVIDU_SECRET=MY_SECRET - DB_TYPE=postgresql - DB_CONNECTION_STRING=postgresql://postgres:password@postgres:5432/aethercall - JWT_SECRET=your-jwt-secret depends_on: postgres: condition: service_healthy openvidu: condition: service_started restart: unless-stopped postgres: image: postgres:15-alpine environment: - POSTGRES_DB=aethercall - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 restart: unless-stopped openvidu: image: openvidu/openvidu-dev:2.29.0 environment: - OPENVIDU_SECRET=MY_SECRET ports: - "4443:4443" restart: unless-stopped volumes: postgres_data: ``` #### Cloud Platform Deployment **Railway** ```bash # Install Railway CLI npm install -g @railway/cli # Login and deploy railway login railway init railway up ``` **Heroku** ```bash # Create Heroku app heroku create your-app-name # Set environment variables heroku config:set NODE_ENV=production heroku config:set OPENVIDU_URL=https://your-openvidu.herokuapp.com:4443 heroku config:set OPENVIDU_SECRET=your-secret heroku config:set JWT_SECRET=your-jwt-secret # Deploy git push heroku main ``` **Render** ```yaml # render.yaml services: - type: web name: aethercall-api env: node plan: starter buildCommand: npm install startCommand: npm start envVars: - key: NODE_ENV value: production - key: OPENVIDU_URL value: https://your-openvidu-instance.onrender.com:4443 ``` ### OpenVidu Server Deployment For production, you'll need a separate OpenVidu server. Options include: 1. **OpenVidu Cloud** (Recommended for production) 2. **Self-hosted on AWS/GCP/Azure** 3. **Docker deployment on your infrastructure** See [OpenVidu Deployment Guide](https://docs.openvidu.io/en/2.29.0/deployment/) for detailed instructions. ## Testing AetherCall includes a comprehensive test suite with 45+ automated tests covering all functionality. ### Running Tests ```bash # Run all tests npm test # Run tests in watch mode npm run test:watch # Run specific test file npm test tests/http-api.test.js # Run tests with coverage report npm run test:coverage # Run tests matching a pattern npm test -- --grep "session" ``` ### Test Coverage - **HTTP API Tests**: All REST endpoints with authentication, validation, and error handling - **Storage Tests**: Database operations across all supported storage types - **OpenVidu Integration Tests**: Session, connection, and recording functionality - **Authentication Tests**: JWT token management and role-based access control ### Test Requirements Before running tests, ensure you have: 1. **OpenVidu Server running** (see Installation section) 2. **Test environment configured**: ```env # .env.test OPENVIDU_URL=http://localhost:4443 OPENVIDU_SECRET=MY_SECRET JWT_SECRET=test-secret-key DB_TYPE=memory LOG_LEVEL=error ``` ### Continuous Integration Tests are automatically run on: - Pull requests - Commits to main branch - Release builds Example GitHub Actions workflow: ```yaml name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: openvidu: image: openvidu/openvidu-dev:2.29.0 ports: - 4443:4443 env: OPENVIDU_SECRET: MY_SECRET steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: '18' - run: npm ci - run: npm test ``` ## Monitoring & Analytics ### Health Check ```bash curl http://localhost:3000/health ``` ### System Metrics ```javascript // Built-in metrics endpoint app.get('/metrics', (req, res) => { res.json({ uptime: process.uptime(), memory: process.memoryUsage(), activeSessions: aetherCall.getActiveSessionCount(), totalConnections: aetherCall.getTotalConnectionCount() }); }); ``` ## Security ### Rate Limiting Built-in rate limiting (100 requests per 15 minutes by default): ```env RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 ``` ### CORS Configuration ```env CORS_ORIGIN=https://yourdomain.com CORS_METHODS=GET,POST,PUT,DELETE ``` ### Token Security - JWT tokens with configurable expiration - Role-based access control (SUBSCRIBER, PUBLISHER, MODERATOR) - Automatic token validation middleware ## Contributing We welcome contributions from the community! Please read our [Contributing Guidelines](CONTRIBUTING.md) before submitting PRs. ### Development Setup 1. **Fork and clone the repository** ```bash git clone https://github.com/YOUR_USERNAME/AetherCall.git cd AetherCall ``` 2. **Install dependencies** ```bash npm install ``` 3. **Set up development environment** ```bash cp .env.example .env # Edit .env with your configuration ``` 4. **Start OpenVidu development server** ```bash docker run -p 4443:4443 --rm \ -e OPENVIDU_SECRET=MY_SECRET \ openvidu/openvidu-dev:2.29.0 ``` 5. **Run in development mode** ```bash npm run dev ``` ### Contribution Guidelines - **Code Style**: We use ESLint and Prettier for code formatting - **Testing**: All new features must include tests - **Documentation**: Update README and API docs for any changes - **Commit Messages**: Use conventional commit format ### Pull Request Process 1. Create a feature branch: `git checkout -b feature/amazing-feature` 2. Make your changes and add tests 3. Run the test suite: `npm test` 4. Update documentation if needed 5. Commit your changes: `git commit -m 'feat: add amazing feature'` 6. Push to your fork: `git push origin feature/amazing-feature` 7. Open a Pull Request with a clear description ## Security ### Reporting Security Issues If you discover a security vulnerability, please email us at security@aethercall.dev instead of using the issue tracker. ### Security Features - **JWT Authentication**: Secure token-based authentication with configurable expiration - **Rate Limiting**: Built-in protection against abuse (configurable) - **CORS Configuration**: Proper cross-origin resource sharing setup - **Input Validation**: All API inputs are validated and sanitized - **Security Headers**: Helmet.js for security headers - **Role-based Access**: SUBSCRIBER, PUBLISHER, MODERATOR roles ;; ## License ;; This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Support and Documentation ### Documentation - **[API Reference](docs/api.md)** - Complete API documentation - **[OpenVidu Integration Guide](https://docs.openvidu.io/)** - OpenVidu documentation ;; - **[Examples Repository](https://github.com/RayenMiri/AetherCall-Examples)** - Sample implementations ;; ### Community Support ;; - **[GitHub Discussions](https://github.com/RayenMiri/AetherCall/discussions)** - Community Q&A ;; - **[Issue Tracker](https://github.com/RayenMiri/AetherCall/issues)** - Bug reports and feature requests ;; - **[Discord Server](https://discord.gg/aethercall)** - Real-time community chat ### Professional Support - **[Email Support](mailto:rayenmiri@gmail.com)** - Technical support ;; - **[Enterprise Support](mailto:enterprise@aethercall.dev)** - Custom solutions and consulting ;; ## Roadmap ;; ### Version 2.0 (Q3 2025) ;; - [ ] **WebSocket API**: Real-time event streaming ;; - [ ] **GraphQL Interface**: Alternative to REST API ;; - [ ] **Advanced Analytics**: Detailed session and participant metrics ;; - [ ] **Load Balancing**: Multi-instance deployment support ;; ### Version 2.1 (Q4 2025) ;; - [ ] **Mobile SDKs**: React Native and Flutter wrappers ;; - [ ] **Advanced Recording**: Individual participant streams ;; - [ ] **Moderation Tools**: Participant management and content moderation - [ ] **Custom Layouts**: Configurable recording layouts ### Future Releases - [ ] **AI Integration**: Automatic transcription and translation - [ ] **Cloud Storage**: Direct integration with AWS S3, Google Cloud Storage - [ ] **Streaming**: RTMP/HLS streaming support - [ ] **SIP Integration**: Traditional phone system integration ## License AetherCall is released under the [MIT License](LICENSE). This means you can: ### ✅ **What You Can Do:** - **Use commercially** - Build and sell applications using AetherCall - **Modify freely** - Customize the code to fit your needs - **Distribute** - Share the software with others - **Sublicense** - Include it in projects with different licenses - **Private use** - Use in proprietary/closed-source projects ### 📋 **Requirements:** - **Include copyright notice** - Keep the original copyright and license notice - **Include license text** - Include the MIT license text in distributions ### 🚫 **Limitations:** - **No warranty** - Software is provided "as-is" without warranties - **No liability** - Authors are not liable for damages or issues ### 💡 **Why MIT License?** The MIT License offers maximum flexibility for developers while being business-friendly: - **Corporate adoption** - Companies can use and modify without legal concerns - **Open source ecosystem** - Compatible with most other open source licenses - **Simple compliance** - Easy to understand and follow requirements - **Innovation encouragement** - Allows derivative works and improvements - **Community growth** - Promotes sharing and collaboration --- **AetherCall** - Professional video calling API for modern applications [![GitHub stars](https://img.shields.io/github/stars/RayenMiri/AetherCall?style=social)](https://github.com/RayenMiri/AetherCall/stargazers) [![GitHub forks](https://img.shields.io/github/forks/RayenMiri/AetherCall?style=social)](https://github.com/RayenMiri/AetherCall/network/members) [![GitHub issues](https://img.shields.io/github/issues/RayenMiri/AetherCall)](https://github.com/RayenMiri/AetherCall/issues) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)