Package Exports
- aethercall
- aethercall/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (aethercall) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
AetherCall
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 dependenciesInstallation
Prerequisites
- Node.js 16.x or higher
- npm or yarn
- OpenVidu Server (Docker recommended)
Install via npm
npm install aethercallInstall from Source
git clone https://github.com/RayenMiri/AetherCall.git
cd AetherCall
npm installQuick Start
1. Start OpenVidu Server
AetherCall requires an OpenVidu server. The easiest way is using Docker:
# Pull and run OpenVidu development server
docker run -p 4443:4443 --rm \
-e OPENVIDU_SECRET=MY_SECRET \
openvidu/openvidu-dev:2.29.02. Configure Environment
Create a .env file in your project root:
# 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=info3. Start the API Server
# Using npm
npm start
# Using yarn
yarn start
# Development mode with auto-reload
npm run dev
# Or using the setup helper
npm run setup4. Using as a Module
See examples/usage-example.js for detailed examples of how to integrate AetherCall into your existing Node.js applications:
# 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 configurationThe API will be available at http://localhost:3000
5. Verify Installation
Check if the API is running:
curl http://localhost:3000/healthExpected response:
{
"status": "healthy",
"timestamp": "2025-07-27T10:00:00.000Z",
"version": "1.0.0"
}API Usage
Base URL
http://localhost:3000/apiAuthentication
All API requests (except health check) require a JWT token:
// 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
// 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
// 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
// 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)
// 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
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
<!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
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
// 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
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
# 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=memoryDeployment
Production Deployment
Environment Variables for Production
# 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=warnDocker Deployment
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
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
# Install Railway CLI
npm install -g @railway/cli
# Login and deploy
railway login
railway init
railway upHeroku
# 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 mainRender
# 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:4443OpenVidu Server Deployment
For production, you'll need a separate OpenVidu server. Options include:
- OpenVidu Cloud (Recommended for production)
- Self-hosted on AWS/GCP/Azure
- Docker deployment on your infrastructure
See OpenVidu Deployment Guide for detailed instructions.
Testing
AetherCall includes a comprehensive test suite with 45+ automated tests covering all functionality.
Running Tests
# 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:
- OpenVidu Server running (see Installation section)
- Test environment configured:
# .env.test
OPENVIDU_URL=http://localhost:4443
OPENVIDU_SECRET=MY_SECRET
JWT_SECRET=test-secret-key
DB_TYPE=memory
LOG_LEVEL=errorContinuous Integration
Tests are automatically run on:
- Pull requests
- Commits to main branch
- Release builds
Example GitHub Actions workflow:
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 testMonitoring & Analytics
Health Check
curl http://localhost:3000/healthSystem Metrics
// 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):
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100CORS Configuration
CORS_ORIGIN=https://yourdomain.com
CORS_METHODS=GET,POST,PUT,DELETEToken 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 before submitting PRs.
Development Setup
- Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/AetherCall.git
cd AetherCall- Install dependencies
npm install- Set up development environment
cp .env.example .env
# Edit .env with your configuration- Start OpenVidu development server
docker run -p 4443:4443 --rm \
-e OPENVIDU_SECRET=MY_SECRET \
openvidu/openvidu-dev:2.29.0- Run in development mode
npm run devContribution 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
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Run the test suite:
npm test - Update documentation if needed
- Commit your changes:
git commit -m 'feat: add amazing feature' - Push to your fork:
git push origin feature/amazing-feature - 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 file for details.
Support and Documentation
Documentation
- API Reference - Complete API documentation
- OpenVidu Integration Guide - OpenVidu documentation ;; - Examples Repository - Sample implementations
;; ### Community Support ;; - GitHub Discussions - Community Q&A ;; - Issue Tracker - Bug reports and feature requests ;; - Discord Server - Real-time community chat
Professional Support
- Email Support - Technical support ;; - Enterprise Support - 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. 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