277
SETUP.md
Archivo normal
277
SETUP.md
Archivo normal
@@ -0,0 +1,277 @@
|
||||
# 🚀 ChatRTC - Complete Setup Guide
|
||||
|
||||
## Project Overview
|
||||
|
||||
ChatRTC is a complete Android WebRTC chat application with a Node.js signaling server. Users can join with just a nickname and enjoy:
|
||||
|
||||
- 💬 **Text messaging with emoji support**
|
||||
- 📹 **Video calling with WebRTC**
|
||||
- 🎤 **Audio calling**
|
||||
- 👥 **Multi-user chat rooms**
|
||||
- 📱 **Modern Android UI**
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
chatrtc/
|
||||
├── app/ # Android Application
|
||||
│ ├── src/main/java/com/chatrtc/app/
|
||||
│ │ ├── MainActivity.java # Nickname entry & permissions
|
||||
│ │ ├── ChatActivity.java # Main chat interface
|
||||
│ │ ├── adapter/
|
||||
│ │ │ └── ChatAdapter.java # Chat message RecyclerView adapter
|
||||
│ │ ├── model/
|
||||
│ │ │ └── ChatMessage.java # Message data model
|
||||
│ │ └── webrtc/
|
||||
│ │ └── WebRTCManager.java # WebRTC connection management
|
||||
│ └── src/main/res/ # Android resources (layouts, drawables, etc.)
|
||||
├── server/ # Node.js Signaling Server
|
||||
│ ├── server.js # Main server implementation
|
||||
│ ├── package.json # Dependencies and scripts
|
||||
│ ├── start-server.sh # Easy startup script
|
||||
│ ├── test-client.js # Server testing utility
|
||||
│ └── README.md # Server documentation
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🛠️ Quick Setup
|
||||
|
||||
### 1. Server Setup (5 minutes)
|
||||
|
||||
```bash
|
||||
# Navigate to server directory
|
||||
cd server
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start the server
|
||||
npm start
|
||||
# or use the convenient script:
|
||||
./start-server.sh start dev
|
||||
```
|
||||
|
||||
**Server will be running at:**
|
||||
- 🌐 **Local**: http://localhost:3000
|
||||
- 📱 **Android Emulator**: http://10.0.2.2:3000
|
||||
- 🔗 **Network**: http://YOUR_IP:3000
|
||||
|
||||
### 2. Android App Setup
|
||||
|
||||
1. **Open in Android Studio**: Open the `chatrtc` folder
|
||||
2. **Update Server URL**: In `WebRTCManager.java`, update:
|
||||
```java
|
||||
private static final String SIGNALING_SERVER_URL = "http://10.0.2.2:3000"; // For emulator
|
||||
// or "http://YOUR_SERVER_IP:3000" for real device
|
||||
```
|
||||
3. **Build & Run**: Connect device/emulator and click Run
|
||||
|
||||
### 3. Test the App
|
||||
|
||||
1. **Grant Permissions**: Allow camera and microphone access
|
||||
2. **Enter Nickname**: Type any nickname and join
|
||||
3. **Start Chatting**: Send messages, toggle video/audio
|
||||
4. **Multi-user**: Run on multiple devices to test P2P connections
|
||||
|
||||
## 🎯 Key Features Implemented
|
||||
|
||||
### Android App Features
|
||||
- ✅ **Simple nickname entry** - No registration required
|
||||
- ✅ **Emoji support** - Full emoji keyboard and rendering
|
||||
- ✅ **WebRTC video/audio** - Peer-to-peer communication
|
||||
- ✅ **Modern chat UI** - Material Design with chat bubbles
|
||||
- ✅ **Media controls** - Toggle video/audio, switch cameras
|
||||
- ✅ **Permission handling** - Smooth camera/mic permission flow
|
||||
|
||||
### Server Features
|
||||
- ✅ **Real-time signaling** - WebRTC offer/answer/ICE exchange
|
||||
- ✅ **Room management** - Multiple users in chat rooms
|
||||
- ✅ **User tracking** - Monitor connections and states
|
||||
- ✅ **REST API** - Server monitoring and room info
|
||||
- ✅ **Error handling** - Comprehensive error management
|
||||
- ✅ **Production ready** - PM2 support, logging, CORS
|
||||
|
||||
## 📱 Android Integration Details
|
||||
|
||||
### WebRTC Connection Flow
|
||||
1. User enters nickname → joins default room
|
||||
2. Server notifies other users → triggers WebRTC offer
|
||||
3. Peer connection established → direct P2P communication
|
||||
4. Data channels used for text messages
|
||||
5. Media streams for audio/video
|
||||
|
||||
### Key Android Components
|
||||
- **MainActivity**: Handles nickname input and permissions
|
||||
- **ChatActivity**: Main UI with video views and chat list
|
||||
- **WebRTCManager**: Manages all WebRTC connections and signaling
|
||||
- **ChatAdapter**: Handles different message types (own/other/system)
|
||||
|
||||
## 🖥️ Server API Reference
|
||||
|
||||
### REST Endpoints
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/` | GET | Server status dashboard (HTML) |
|
||||
| `/api/status` | GET | Server statistics (JSON) |
|
||||
| `/api/rooms` | GET | List all active rooms |
|
||||
| `/api/rooms/:id` | GET | Specific room information |
|
||||
| `/health` | GET | Health check endpoint |
|
||||
|
||||
### Socket.IO Events
|
||||
**Client → Server:**
|
||||
- `join-room` - Join chat room with nickname
|
||||
- `offer/answer/ice-candidate` - WebRTC signaling
|
||||
- `media-state` - Video/audio state updates
|
||||
- `chat-message` - Text message fallback
|
||||
|
||||
**Server → Client:**
|
||||
- `joined-room` - Successful room join confirmation
|
||||
- `user-joined/user-left` - User connection notifications
|
||||
- `offer/answer/ice-candidate` - WebRTC signaling relay
|
||||
- `user-media-state` - Media state broadcasts
|
||||
|
||||
## 🔧 Development & Testing
|
||||
|
||||
### Server Commands
|
||||
```bash
|
||||
# Development with auto-restart
|
||||
npm run dev
|
||||
|
||||
# Production mode
|
||||
npm start
|
||||
|
||||
# Process manager (recommended for production)
|
||||
./start-server.sh start pm2
|
||||
|
||||
# Test server connectivity
|
||||
./start-server.sh test
|
||||
|
||||
# Show server info
|
||||
./start-server.sh info
|
||||
```
|
||||
|
||||
### Android Development
|
||||
```bash
|
||||
# Build debug APK
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Install on connected device
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# View app logs
|
||||
adb logcat | grep ChatRTC
|
||||
```
|
||||
|
||||
### Testing Multi-User Scenarios
|
||||
1. **Multiple Devices**: Install on different Android devices
|
||||
2. **Emulator + Device**: Run on emulator and real device
|
||||
3. **Multiple Emulators**: Create multiple AVDs for testing
|
||||
|
||||
## 🚀 Production Deployment
|
||||
|
||||
### Server Deployment
|
||||
```bash
|
||||
# Using PM2 (recommended)
|
||||
npm install -g pm2
|
||||
npm run pm2:start
|
||||
|
||||
# Using Docker
|
||||
docker build -t chatrtc-server .
|
||||
docker run -p 3000:3000 chatrtc-server
|
||||
|
||||
# Manual deployment
|
||||
NODE_ENV=production npm start
|
||||
```
|
||||
|
||||
### Android Release
|
||||
```bash
|
||||
# Generate release APK
|
||||
./gradlew assembleRelease
|
||||
|
||||
# Generate signed APK (configure keystore first)
|
||||
./gradlew bundleRelease
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Server won't start:**
|
||||
- Check if port 3000 is available: `lsof -i :3000`
|
||||
- Verify Node.js installation: `node --version`
|
||||
|
||||
**Android connection failed:**
|
||||
- Verify server URL in WebRTCManager.java
|
||||
- Check network connectivity
|
||||
- Ensure server is accessible from device network
|
||||
|
||||
**WebRTC not working:**
|
||||
- Grant camera/microphone permissions
|
||||
- Check for HTTPS requirement in production
|
||||
- Verify STUN server connectivity
|
||||
|
||||
**No video/audio:**
|
||||
- Test on real device (emulator has limited media support)
|
||||
- Check device camera/microphone functionality
|
||||
- Verify WebRTC peer connection establishment
|
||||
|
||||
### Debug Commands
|
||||
```bash
|
||||
# Check server status
|
||||
curl http://localhost:3000/api/status
|
||||
|
||||
# Monitor server logs
|
||||
tail -f server/logs/chatrtc-server.log
|
||||
|
||||
# Android logs
|
||||
adb logcat | grep -E "(WebRTC|ChatRTC|WebRTCManager)"
|
||||
```
|
||||
|
||||
## 📊 Performance & Scalability
|
||||
|
||||
### Current Limitations
|
||||
- **P2P only**: Direct connections between 2 users
|
||||
- **Single server**: One signaling server instance
|
||||
- **Memory-based storage**: User data not persisted
|
||||
|
||||
### Scaling Options
|
||||
- **MCU/SFU**: Media server for group calls
|
||||
- **Redis**: Distributed session storage
|
||||
- **Load balancer**: Multiple server instances
|
||||
- **Database**: Persistent user/room data
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
### UI Customization
|
||||
- **Colors**: Edit `app/src/main/res/values/colors.xml`
|
||||
- **Layouts**: Modify XML layouts in `res/layout/`
|
||||
- **Icons**: Replace drawable resources
|
||||
- **Themes**: Update `res/values/themes.xml`
|
||||
|
||||
### Server Customization
|
||||
- **Room limits**: Modify server configuration
|
||||
- **STUN/TURN servers**: Update WebRTC configuration
|
||||
- **Message limits**: Configure rate limiting
|
||||
- **Logging**: Adjust log levels and destinations
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - Feel free to use and modify for your projects!
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create feature branch: `git checkout -b feature/amazing-feature`
|
||||
3. Commit changes: `git commit -m 'Add amazing feature'`
|
||||
4. Push to branch: `git push origin feature/amazing-feature`
|
||||
5. Open Pull Request
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
- 📝 **Issues**: Create GitHub issues for bugs/features
|
||||
- 📚 **Documentation**: Check server and app README files
|
||||
- 🔍 **Debugging**: Enable debug logs for detailed information
|
||||
|
||||
---
|
||||
|
||||
**🎉 You're all set! Your ChatRTC application is ready for development and deployment.**
|
||||
@@ -40,7 +40,9 @@ import io.socket.client.Socket;
|
||||
public class WebRTCManager {
|
||||
|
||||
private static final String TAG = "WebRTCManager";
|
||||
private static final String SIGNALING_SERVER_URL = "https://your-signaling-server.com"; // Replace with your server
|
||||
private static final String SIGNALING_SERVER_URL = "http://10.0.2.2:3000"; // For Android emulator
|
||||
// For real device on same network, use: "http://192.168.1.XXX:3000"
|
||||
// For production: "https://your-server.com"
|
||||
|
||||
public interface WebRTCListener {
|
||||
void onMessageReceived(String senderNickname, String message);
|
||||
@@ -208,13 +210,25 @@ public class WebRTCManager {
|
||||
listener.onDisconnected();
|
||||
});
|
||||
|
||||
socket.on("joined-room", args -> {
|
||||
Log.d(TAG, "Successfully joined room");
|
||||
JsonObject data = gson.fromJson(args[0].toString(), JsonObject.class);
|
||||
String roomId = data.get("roomId").getAsString();
|
||||
// Room joined successfully, ready for WebRTC
|
||||
});
|
||||
|
||||
socket.on("user-joined", args -> {
|
||||
String userNickname = (String) args[0];
|
||||
JsonObject data = gson.fromJson(args[0].toString(), JsonObject.class);
|
||||
String userNickname = data.get("nickname").getAsString();
|
||||
listener.onUserJoined(userNickname);
|
||||
|
||||
// Initiate WebRTC connection for new user
|
||||
createOffer();
|
||||
});
|
||||
|
||||
socket.on("user-left", args -> {
|
||||
String userNickname = (String) args[0];
|
||||
JsonObject data = gson.fromJson(args[0].toString(), JsonObject.class);
|
||||
String userNickname = data.get("nickname").getAsString();
|
||||
listener.onUserLeft(userNickname);
|
||||
});
|
||||
|
||||
@@ -233,6 +247,13 @@ public class WebRTCManager {
|
||||
handleIceCandidate(data);
|
||||
});
|
||||
|
||||
socket.on("error", args -> {
|
||||
JsonObject data = gson.fromJson(args[0].toString(), JsonObject.class);
|
||||
String errorMessage = data.get("message").getAsString();
|
||||
Log.e(TAG, "Server error: " + errorMessage);
|
||||
listener.onError(errorMessage);
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -244,7 +265,10 @@ public class WebRTCManager {
|
||||
public void joinRoom(String nickname) {
|
||||
this.nickname = nickname;
|
||||
if (socket != null && socket.connected()) {
|
||||
socket.emit("join-room", nickname);
|
||||
JsonObject joinData = new JsonObject();
|
||||
joinData.addProperty("nickname", nickname);
|
||||
joinData.addProperty("roomId", "main-chat"); // Default room
|
||||
socket.emit("join-room", joinData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,48 +309,101 @@ public class WebRTCManager {
|
||||
}
|
||||
|
||||
private void handleOffer(JsonObject data) {
|
||||
SessionDescription offer = new SessionDescription(
|
||||
JsonObject offer = data.getAsJsonObject("offer");
|
||||
SessionDescription offerSdp = new SessionDescription(
|
||||
SessionDescription.Type.OFFER,
|
||||
data.get("sdp").getAsString()
|
||||
offer.get("sdp").getAsString()
|
||||
);
|
||||
|
||||
peerConnection.setRemoteDescription(new SdpObserver(), offer);
|
||||
peerConnection.setRemoteDescription(new SdpObserver(), offerSdp);
|
||||
|
||||
// Create answer
|
||||
MediaConstraints constraints = new MediaConstraints();
|
||||
constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
|
||||
constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
|
||||
|
||||
peerConnection.createAnswer(new SdpObserver() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sessionDescription) {
|
||||
Log.d(TAG, "Answer created successfully");
|
||||
peerConnection.setLocalDescription(new SdpObserver(), sessionDescription);
|
||||
|
||||
JsonObject answerData = new JsonObject();
|
||||
answerData.addProperty("type", sessionDescription.type.canonicalForm());
|
||||
answerData.addProperty("sdp", sessionDescription.description);
|
||||
JsonObject answer = new JsonObject();
|
||||
answer.addProperty("type", sessionDescription.type.canonicalForm());
|
||||
answer.addProperty("sdp", sessionDescription.description);
|
||||
answerData.add("answer", answer);
|
||||
|
||||
// Send to the specific user who sent the offer
|
||||
if (data.has("fromSocketId")) {
|
||||
answerData.addProperty("targetSocketId", data.get("fromSocketId").getAsString());
|
||||
}
|
||||
|
||||
socket.emit("answer", answerData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
Log.e(TAG, "Failed to create answer: " + error);
|
||||
listener.onError("Failed to create answer: " + error);
|
||||
}
|
||||
}, constraints);
|
||||
}
|
||||
|
||||
private void handleAnswer(JsonObject data) {
|
||||
SessionDescription answer = new SessionDescription(
|
||||
JsonObject answer = data.getAsJsonObject("answer");
|
||||
SessionDescription answerSdp = new SessionDescription(
|
||||
SessionDescription.Type.ANSWER,
|
||||
data.get("sdp").getAsString()
|
||||
answer.get("sdp").getAsString()
|
||||
);
|
||||
|
||||
peerConnection.setRemoteDescription(new SdpObserver(), answer);
|
||||
peerConnection.setRemoteDescription(new SdpObserver(), answerSdp);
|
||||
}
|
||||
|
||||
private void handleIceCandidate(JsonObject data) {
|
||||
JsonObject candidateObj = data.getAsJsonObject("candidate");
|
||||
IceCandidate iceCandidate = new IceCandidate(
|
||||
data.get("sdpMid").getAsString(),
|
||||
data.get("sdpMLineIndex").getAsInt(),
|
||||
data.get("candidate").getAsString()
|
||||
candidateObj.get("sdpMid").getAsString(),
|
||||
candidateObj.get("sdpMLineIndex").getAsInt(),
|
||||
candidateObj.get("candidate").getAsString()
|
||||
);
|
||||
|
||||
peerConnection.addIceCandidate(iceCandidate);
|
||||
}
|
||||
|
||||
private void createOffer() {
|
||||
if (peerConnection == null) {
|
||||
Log.w(TAG, "PeerConnection not initialized when creating offer");
|
||||
return;
|
||||
}
|
||||
|
||||
MediaConstraints constraints = new MediaConstraints();
|
||||
constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
|
||||
constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
|
||||
|
||||
peerConnection.createOffer(new SdpObserver() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sessionDescription) {
|
||||
Log.d(TAG, "Offer created successfully");
|
||||
peerConnection.setLocalDescription(new SdpObserver(), sessionDescription);
|
||||
|
||||
JsonObject offerData = new JsonObject();
|
||||
JsonObject offer = new JsonObject();
|
||||
offer.addProperty("type", sessionDescription.type.canonicalForm());
|
||||
offer.addProperty("sdp", sessionDescription.description);
|
||||
offerData.add("offer", offer);
|
||||
|
||||
socket.emit("offer", offerData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
Log.e(TAG, "Failed to create offer: " + error);
|
||||
listener.onError("Failed to create offer: " + error);
|
||||
}
|
||||
}, constraints);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (localVideoTrack != null) {
|
||||
localVideoTrack.dispose();
|
||||
@@ -389,9 +466,11 @@ public class WebRTCManager {
|
||||
Log.d(TAG, "onIceCandidate: " + iceCandidate);
|
||||
|
||||
JsonObject candidateData = new JsonObject();
|
||||
candidateData.addProperty("candidate", iceCandidate.sdp);
|
||||
candidateData.addProperty("sdpMid", iceCandidate.sdpMid);
|
||||
candidateData.addProperty("sdpMLineIndex", iceCandidate.sdpMLineIndex);
|
||||
JsonObject candidate = new JsonObject();
|
||||
candidate.addProperty("candidate", iceCandidate.sdp);
|
||||
candidate.addProperty("sdpMid", iceCandidate.sdpMid);
|
||||
candidate.addProperty("sdpMLineIndex", iceCandidate.sdpMLineIndex);
|
||||
candidateData.add("candidate", candidate);
|
||||
|
||||
socket.emit("ice-candidate", candidateData);
|
||||
}
|
||||
|
||||
35
server/.env.example
Archivo normal
35
server/.env.example
Archivo normal
@@ -0,0 +1,35 @@
|
||||
# Environment Configuration
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
HOST=0.0.0.0
|
||||
|
||||
# CORS Configuration (comma-separated origins)
|
||||
ALLOWED_ORIGINS=*
|
||||
|
||||
# Socket.IO Configuration
|
||||
SOCKET_IO_PING_TIMEOUT=60000
|
||||
SOCKET_IO_PING_INTERVAL=25000
|
||||
|
||||
# Room Configuration
|
||||
DEFAULT_ROOM_NAME=main-chat
|
||||
MAX_USERS_PER_ROOM=10
|
||||
MAX_MESSAGE_LENGTH=1000
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_WINDOW_MS=60000
|
||||
RATE_LIMIT_MAX_REQUESTS=100
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=logs/chatrtc-server.log
|
||||
|
||||
# Security
|
||||
ENABLE_HELMET=true
|
||||
TRUST_PROXY=false
|
||||
|
||||
# Production Configuration (uncomment for production)
|
||||
# NODE_ENV=production
|
||||
# PORT=443
|
||||
# SSL_KEY_PATH=/path/to/private-key.pem
|
||||
# SSL_CERT_PATH=/path/to/certificate.pem
|
||||
# SSL_CA_PATH=/path/to/ca-bundle.pem
|
||||
124
server/.gitignore
vendido
Archivo normal
124
server/.gitignore
vendido
Archivo normal
@@ -0,0 +1,124 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.production
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# PM2 logs and pids
|
||||
.pm2/
|
||||
|
||||
# SSL certificates
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
345
server/README.md
Archivo normal
345
server/README.md
Archivo normal
@@ -0,0 +1,345 @@
|
||||
# ChatRTC Signaling Server
|
||||
|
||||
Advanced WebRTC signaling server built with Node.js and Socket.IO for the ChatRTC Android application.
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **Real-time WebRTC Signaling** - Handle offer/answer/ICE candidate exchange
|
||||
- 🏠 **Room Management** - Support for multiple chat rooms
|
||||
- 👥 **User Management** - Track users, nicknames, and states
|
||||
- 💬 **Message Fallback** - Backup text messaging via Socket.IO
|
||||
- 📊 **REST API** - Monitor server status and room information
|
||||
- 🔒 **Error Handling** - Comprehensive error handling and logging
|
||||
- 📱 **Mobile Optimized** - Optimized for Android WebRTC connections
|
||||
- 🌐 **CORS Support** - Flexible cross-origin configuration
|
||||
- ⚡ **Performance** - Built for high-performance real-time communication
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
cd server
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
Copy the environment configuration:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` file with your settings:
|
||||
```env
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
HOST=0.0.0.0
|
||||
ALLOWED_ORIGINS=*
|
||||
```
|
||||
|
||||
### 3. Start the Server
|
||||
|
||||
**Development mode:**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Production mode:**
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
**With PM2 (recommended for production):**
|
||||
```bash
|
||||
npm run pm2:start
|
||||
```
|
||||
|
||||
### 4. Verify Installation
|
||||
|
||||
Open your browser and navigate to `http://localhost:3000` to see the server status page.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### REST API
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/` | GET | Server status page (HTML) |
|
||||
| `/api/status` | GET | Server statistics (JSON) |
|
||||
| `/api/rooms` | GET | List all active rooms |
|
||||
| `/api/rooms/:roomId` | GET | Get specific room information |
|
||||
| `/health` | GET | Health check endpoint |
|
||||
|
||||
### Example API Responses
|
||||
|
||||
**GET /api/status**
|
||||
```json
|
||||
{
|
||||
"status": "running",
|
||||
"timestamp": "2025-06-15T10:30:00.000Z",
|
||||
"connectedUsers": 5,
|
||||
"activeRooms": 2,
|
||||
"uptime": 3600
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/rooms**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"roomId": "main-chat",
|
||||
"userCount": 3,
|
||||
"users": [
|
||||
{
|
||||
"nickname": "Alice",
|
||||
"joinedAt": "2025-06-15T10:25:00.000Z"
|
||||
},
|
||||
{
|
||||
"nickname": "Bob",
|
||||
"joinedAt": "2025-06-15T10:28:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Socket.IO Events
|
||||
|
||||
### Client → Server
|
||||
|
||||
| Event | Data | Description |
|
||||
|-------|------|-------------|
|
||||
| `join-room` | `{nickname, roomId?}` | Join a chat room |
|
||||
| `offer` | `{targetSocketId?, offer}` | Send WebRTC offer |
|
||||
| `answer` | `{targetSocketId, answer}` | Send WebRTC answer |
|
||||
| `ice-candidate` | `{targetSocketId?, candidate}` | Send ICE candidate |
|
||||
| `media-state` | `{isVideoEnabled, isAudioEnabled}` | Update media state |
|
||||
| `chat-message` | `{message}` | Send text message (fallback) |
|
||||
| `get-rooms` | - | Request room list |
|
||||
| `get-room-users` | - | Request current room users |
|
||||
| `ping` | - | Connection health check |
|
||||
|
||||
### Server → Client
|
||||
|
||||
| Event | Data | Description |
|
||||
|-------|------|-------------|
|
||||
| `joined-room` | `{roomId, nickname, users}` | Successful room join |
|
||||
| `user-joined` | `{socketId, nickname, joinedAt}` | New user joined |
|
||||
| `user-left` | `{socketId, nickname, reason}` | User disconnected |
|
||||
| `offer` | `{fromSocketId, fromNickname, offer}` | Received WebRTC offer |
|
||||
| `answer` | `{fromSocketId, fromNickname, answer}` | Received WebRTC answer |
|
||||
| `ice-candidate` | `{fromSocketId, fromNickname, candidate}` | Received ICE candidate |
|
||||
| `user-media-state` | `{socketId, nickname, isVideoEnabled, isAudioEnabled}` | User media state changed |
|
||||
| `chat-message` | `{fromSocketId, fromNickname, message, timestamp}` | Received text message |
|
||||
| `rooms-list` | `[{roomId, userCount, users}]` | Available rooms |
|
||||
| `room-users` | `{roomId, users}` | Current room users |
|
||||
| `error` | `{message, code?}` | Error occurred |
|
||||
| `pong` | `{timestamp}` | Ping response |
|
||||
|
||||
## Android Integration
|
||||
|
||||
### Update WebRTCManager.java
|
||||
|
||||
Replace the signaling server URL in your Android app:
|
||||
|
||||
```java
|
||||
// In WebRTCManager.java
|
||||
private static final String SIGNALING_SERVER_URL = "http://YOUR_SERVER_IP:3000";
|
||||
|
||||
// For local development with Android emulator:
|
||||
private static final String SIGNALING_SERVER_URL = "http://10.0.2.2:3000";
|
||||
|
||||
// For real device on same network:
|
||||
private static final String SIGNALING_SERVER_URL = "http://192.168.1.XXX:3000";
|
||||
```
|
||||
|
||||
### Socket.IO Connection Example
|
||||
|
||||
```java
|
||||
// Connect to server
|
||||
socket = IO.socket(SIGNALING_SERVER_URL);
|
||||
|
||||
// Join room
|
||||
JsonObject joinData = new JsonObject();
|
||||
joinData.addProperty("nickname", nickname);
|
||||
joinData.addProperty("roomId", "main-chat"); // optional
|
||||
socket.emit("join-room", joinData);
|
||||
|
||||
// Handle room joined
|
||||
socket.on("joined-room", args -> {
|
||||
JsonObject data = gson.fromJson(args[0].toString(), JsonObject.class);
|
||||
String roomId = data.get("roomId").getAsString();
|
||||
// Handle successful join
|
||||
});
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### 1. Environment Setup
|
||||
|
||||
```bash
|
||||
# Create production environment file
|
||||
cp .env.example .env
|
||||
|
||||
# Edit for production
|
||||
nano .env
|
||||
```
|
||||
|
||||
Set production values:
|
||||
```env
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
HOST=0.0.0.0
|
||||
ALLOWED_ORIGINS=https://yourdomain.com
|
||||
```
|
||||
|
||||
### 2. SSL/HTTPS (Recommended)
|
||||
|
||||
For production, use HTTPS. You can:
|
||||
- Use a reverse proxy (nginx, Apache)
|
||||
- Configure SSL directly in the server
|
||||
- Use a load balancer with SSL termination
|
||||
|
||||
### 3. Process Management
|
||||
|
||||
**Using PM2:**
|
||||
```bash
|
||||
npm install -g pm2
|
||||
npm run pm2:start
|
||||
|
||||
# Monitor
|
||||
pm2 status
|
||||
pm2 logs chatrtc-server
|
||||
pm2 monit
|
||||
```
|
||||
|
||||
**Using Docker:**
|
||||
```dockerfile
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install --production
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
### 4. Performance Optimization
|
||||
|
||||
- Enable gzip compression (included)
|
||||
- Use clustering for multiple CPU cores
|
||||
- Configure proper logging levels
|
||||
- Set up monitoring and alerting
|
||||
- Use Redis for scaling (future enhancement)
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Basic health check
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Detailed status
|
||||
curl http://localhost:3000/api/status
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# View logs (PM2)
|
||||
pm2 logs chatrtc-server
|
||||
|
||||
# View logs (direct)
|
||||
tail -f logs/chatrtc-server.log
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Port already in use**
|
||||
```bash
|
||||
# Check what's using the port
|
||||
lsof -i :3000
|
||||
# Kill the process or change PORT in .env
|
||||
```
|
||||
|
||||
2. **CORS errors from Android**
|
||||
- Ensure `ALLOWED_ORIGINS=*` in .env
|
||||
- Check network security config in Android app
|
||||
|
||||
3. **Connection timeouts**
|
||||
- Verify firewall settings
|
||||
- Check if port is accessible from client network
|
||||
|
||||
4. **Socket.IO connection fails**
|
||||
- Enable polling transport as fallback
|
||||
- Check network connectivity
|
||||
- Verify server URL in Android app
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable detailed logging:
|
||||
```env
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
### Testing Connection
|
||||
|
||||
Use a Socket.IO client to test:
|
||||
```javascript
|
||||
const io = require('socket.io-client');
|
||||
const socket = io('http://localhost:3000');
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected to server');
|
||||
socket.emit('join-room', { nickname: 'TestUser' });
|
||||
});
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
server/
|
||||
├── server.js # Main server file
|
||||
├── package.json # Dependencies and scripts
|
||||
├── ecosystem.config.js # PM2 configuration
|
||||
├── .env.example # Environment template
|
||||
├── .gitignore # Git ignore rules
|
||||
├── README.md # This file
|
||||
└── logs/ # Log files (created automatically)
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
|
||||
## Support
|
||||
|
||||
- Create an issue on GitHub
|
||||
- Check the troubleshooting section
|
||||
- Review server logs for errors
|
||||
23
server/ecosystem.config.js
Archivo normal
23
server/ecosystem.config.js
Archivo normal
@@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'chatrtc-server',
|
||||
script: 'server.js',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '1G',
|
||||
env: {
|
||||
NODE_ENV: 'development',
|
||||
PORT: 3000
|
||||
},
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000
|
||||
},
|
||||
log_file: 'logs/combined.log',
|
||||
out_file: 'logs/out.log',
|
||||
error_file: 'logs/error.log',
|
||||
log_date_format: 'YYYY-MM-DD HH:mm Z',
|
||||
merge_logs: true
|
||||
}]
|
||||
};
|
||||
8005
server/package-lock.json
generado
Archivo normal
8005
server/package-lock.json
generado
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar Diff
58
server/package.json
Archivo normal
58
server/package.json
Archivo normal
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "chatrtc-signaling-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Advanced WebRTC signaling server for ChatRTC Android app with room management and real-time communication",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"test": "jest",
|
||||
"lint": "eslint .",
|
||||
"pm2:start": "pm2 start ecosystem.config.js",
|
||||
"pm2:stop": "pm2 stop chatrtc-server",
|
||||
"pm2:restart": "pm2 restart chatrtc-server",
|
||||
"pm2:logs": "pm2 logs chatrtc-server"
|
||||
},
|
||||
"keywords": [
|
||||
"webrtc",
|
||||
"signaling",
|
||||
"socket.io",
|
||||
"chat",
|
||||
"android",
|
||||
"nodejs",
|
||||
"real-time",
|
||||
"video-chat",
|
||||
"audio-chat"
|
||||
],
|
||||
"author": "ChatRTC Team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"helmet": "^7.1.0",
|
||||
"morgan": "^1.10.0",
|
||||
"socket.io": "^4.7.4",
|
||||
"socket.io-client": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.56.0",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"pm2": "^5.3.0",
|
||||
"supertest": "^6.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/your-username/chatrtc-signaling-server.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/your-username/chatrtc-signaling-server/issues"
|
||||
},
|
||||
"homepage": "https://github.com/your-username/chatrtc-signaling-server#readme"
|
||||
}
|
||||
541
server/server.js
Archivo normal
541
server/server.js
Archivo normal
@@ -0,0 +1,541 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const socketIo = require('socket.io');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = socketIo(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
allowedHeaders: ["Content-Type"],
|
||||
credentials: true
|
||||
},
|
||||
transports: ['websocket', 'polling']
|
||||
});
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// Store active users and rooms
|
||||
const users = new Map(); // socketId -> user info
|
||||
const rooms = new Map(); // roomId -> Set of socketIds
|
||||
const userRooms = new Map(); // socketId -> roomId
|
||||
|
||||
// Default room for all users
|
||||
const DEFAULT_ROOM = 'main-chat';
|
||||
|
||||
// Utility functions
|
||||
function getUserInfo(socketId) {
|
||||
return users.get(socketId);
|
||||
}
|
||||
|
||||
function addUserToRoom(socketId, roomId = DEFAULT_ROOM) {
|
||||
if (!rooms.has(roomId)) {
|
||||
rooms.set(roomId, new Set());
|
||||
}
|
||||
rooms.get(roomId).add(socketId);
|
||||
userRooms.set(socketId, roomId);
|
||||
}
|
||||
|
||||
function removeUserFromRoom(socketId) {
|
||||
const roomId = userRooms.get(socketId);
|
||||
if (roomId && rooms.has(roomId)) {
|
||||
rooms.get(roomId).delete(socketId);
|
||||
if (rooms.get(roomId).size === 0) {
|
||||
rooms.delete(roomId);
|
||||
}
|
||||
}
|
||||
userRooms.delete(socketId);
|
||||
}
|
||||
|
||||
function getRoomUsers(roomId) {
|
||||
const roomUsers = [];
|
||||
if (rooms.has(roomId)) {
|
||||
for (const socketId of rooms.get(roomId)) {
|
||||
const user = users.get(socketId);
|
||||
if (user) {
|
||||
roomUsers.push({
|
||||
socketId,
|
||||
nickname: user.nickname,
|
||||
joinedAt: user.joinedAt
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return roomUsers;
|
||||
}
|
||||
|
||||
function broadcastToRoom(roomId, event, data, exceptSocketId = null) {
|
||||
if (rooms.has(roomId)) {
|
||||
for (const socketId of rooms.get(roomId)) {
|
||||
if (socketId !== exceptSocketId) {
|
||||
io.to(socketId).emit(event, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Socket.IO connection handling
|
||||
io.on('connection', (socket) => {
|
||||
console.log(`[${new Date().toISOString()}] User connected: ${socket.id}`);
|
||||
|
||||
// Handle user joining
|
||||
socket.on('join-room', (data) => {
|
||||
try {
|
||||
const { nickname, roomId = DEFAULT_ROOM } = typeof data === 'string'
|
||||
? { nickname: data, roomId: DEFAULT_ROOM }
|
||||
: data;
|
||||
|
||||
if (!nickname || nickname.trim().length === 0) {
|
||||
socket.emit('error', { message: 'Nickname is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if nickname is already taken in the room
|
||||
const roomUsers = getRoomUsers(roomId);
|
||||
const nicknameExists = roomUsers.some(user =>
|
||||
user.nickname.toLowerCase() === nickname.toLowerCase()
|
||||
);
|
||||
|
||||
if (nicknameExists) {
|
||||
socket.emit('error', {
|
||||
message: 'Nickname already taken in this room',
|
||||
code: 'NICKNAME_TAKEN'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Store user information
|
||||
const userInfo = {
|
||||
nickname: nickname.trim(),
|
||||
roomId,
|
||||
joinedAt: new Date().toISOString(),
|
||||
isVideoEnabled: true,
|
||||
isAudioEnabled: true
|
||||
};
|
||||
|
||||
users.set(socket.id, userInfo);
|
||||
addUserToRoom(socket.id, roomId);
|
||||
|
||||
// Join socket room
|
||||
socket.join(roomId);
|
||||
|
||||
// Notify user they successfully joined
|
||||
socket.emit('joined-room', {
|
||||
roomId,
|
||||
nickname: userInfo.nickname,
|
||||
users: getRoomUsers(roomId)
|
||||
});
|
||||
|
||||
// Notify others in the room
|
||||
broadcastToRoom(roomId, 'user-joined', {
|
||||
socketId: socket.id,
|
||||
nickname: userInfo.nickname,
|
||||
joinedAt: userInfo.joinedAt
|
||||
}, socket.id);
|
||||
|
||||
console.log(`[${new Date().toISOString()}] ${userInfo.nickname} joined room: ${roomId}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in join-room:', error);
|
||||
socket.emit('error', { message: 'Failed to join room' });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle WebRTC signaling - Offer
|
||||
socket.on('offer', (data) => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (!user) {
|
||||
socket.emit('error', { message: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetSocketId, offer } = data;
|
||||
|
||||
if (targetSocketId) {
|
||||
// Send to specific user
|
||||
io.to(targetSocketId).emit('offer', {
|
||||
fromSocketId: socket.id,
|
||||
fromNickname: user.nickname,
|
||||
offer
|
||||
});
|
||||
} else {
|
||||
// Broadcast to room (for group calls)
|
||||
broadcastToRoom(user.roomId, 'offer', {
|
||||
fromSocketId: socket.id,
|
||||
fromNickname: user.nickname,
|
||||
offer
|
||||
}, socket.id);
|
||||
}
|
||||
|
||||
console.log(`[${new Date().toISOString()}] Offer from ${user.nickname} to ${targetSocketId || 'room'}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in offer:', error);
|
||||
socket.emit('error', { message: 'Failed to send offer' });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle WebRTC signaling - Answer
|
||||
socket.on('answer', (data) => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (!user) {
|
||||
socket.emit('error', { message: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetSocketId, answer } = data;
|
||||
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit('answer', {
|
||||
fromSocketId: socket.id,
|
||||
fromNickname: user.nickname,
|
||||
answer
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`[${new Date().toISOString()}] Answer from ${user.nickname} to ${targetSocketId}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in answer:', error);
|
||||
socket.emit('error', { message: 'Failed to send answer' });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle WebRTC signaling - ICE Candidate
|
||||
socket.on('ice-candidate', (data) => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (!user) {
|
||||
socket.emit('error', { message: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetSocketId, candidate } = data;
|
||||
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit('ice-candidate', {
|
||||
fromSocketId: socket.id,
|
||||
fromNickname: user.nickname,
|
||||
candidate
|
||||
});
|
||||
} else {
|
||||
// Broadcast to room
|
||||
broadcastToRoom(user.roomId, 'ice-candidate', {
|
||||
fromSocketId: socket.id,
|
||||
fromNickname: user.nickname,
|
||||
candidate
|
||||
}, socket.id);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in ice-candidate:', error);
|
||||
socket.emit('error', { message: 'Failed to send ICE candidate' });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle media state changes
|
||||
socket.on('media-state', (data) => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (!user) {
|
||||
socket.emit('error', { message: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { isVideoEnabled, isAudioEnabled } = data;
|
||||
|
||||
// Update user state
|
||||
user.isVideoEnabled = isVideoEnabled;
|
||||
user.isAudioEnabled = isAudioEnabled;
|
||||
|
||||
// Broadcast to room
|
||||
broadcastToRoom(user.roomId, 'user-media-state', {
|
||||
socketId: socket.id,
|
||||
nickname: user.nickname,
|
||||
isVideoEnabled,
|
||||
isAudioEnabled
|
||||
}, socket.id);
|
||||
|
||||
console.log(`[${new Date().toISOString()}] ${user.nickname} media state - Video: ${isVideoEnabled}, Audio: ${isAudioEnabled}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in media-state:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle chat messages (backup for data channel failures)
|
||||
socket.on('chat-message', (data) => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (!user) {
|
||||
socket.emit('error', { message: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { message } = data;
|
||||
if (!message || message.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
fromSocketId: socket.id,
|
||||
fromNickname: user.nickname,
|
||||
message: message.trim(),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Broadcast to room
|
||||
broadcastToRoom(user.roomId, 'chat-message', messageData, socket.id);
|
||||
|
||||
console.log(`[${new Date().toISOString()}] Message from ${user.nickname}: ${message}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in chat-message:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle room list request
|
||||
socket.on('get-rooms', () => {
|
||||
try {
|
||||
const roomList = [];
|
||||
for (const [roomId, userSet] of rooms.entries()) {
|
||||
roomList.push({
|
||||
roomId,
|
||||
userCount: userSet.size,
|
||||
users: getRoomUsers(roomId).map(u => ({
|
||||
nickname: u.nickname,
|
||||
joinedAt: u.joinedAt
|
||||
}))
|
||||
});
|
||||
}
|
||||
socket.emit('rooms-list', roomList);
|
||||
} catch (error) {
|
||||
console.error('Error getting rooms:', error);
|
||||
socket.emit('error', { message: 'Failed to get rooms' });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle user list request for current room
|
||||
socket.on('get-room-users', () => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (user) {
|
||||
const roomUsers = getRoomUsers(user.roomId);
|
||||
socket.emit('room-users', {
|
||||
roomId: user.roomId,
|
||||
users: roomUsers
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting room users:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle ping for connection monitoring
|
||||
socket.on('ping', () => {
|
||||
socket.emit('pong', { timestamp: Date.now() });
|
||||
});
|
||||
|
||||
// Handle disconnection
|
||||
socket.on('disconnect', (reason) => {
|
||||
try {
|
||||
const user = getUserInfo(socket.id);
|
||||
if (user) {
|
||||
// Notify others in the room
|
||||
broadcastToRoom(user.roomId, 'user-left', {
|
||||
socketId: socket.id,
|
||||
nickname: user.nickname,
|
||||
reason
|
||||
});
|
||||
|
||||
console.log(`[${new Date().toISOString()}] ${user.nickname} left room: ${user.roomId} (${reason})`);
|
||||
|
||||
// Clean up
|
||||
removeUserFromRoom(socket.id);
|
||||
users.delete(socket.id);
|
||||
} else {
|
||||
console.log(`[${new Date().toISOString()}] Unknown user disconnected: ${socket.id} (${reason})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling disconnect:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
socket.on('error', (error) => {
|
||||
console.error(`[${new Date().toISOString()}] Socket error for ${socket.id}:`, error);
|
||||
});
|
||||
});
|
||||
|
||||
// REST API endpoints
|
||||
app.get('/api/status', (req, res) => {
|
||||
res.json({
|
||||
status: 'running',
|
||||
timestamp: new Date().toISOString(),
|
||||
connectedUsers: users.size,
|
||||
activeRooms: rooms.size,
|
||||
uptime: process.uptime()
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/rooms', (req, res) => {
|
||||
const roomList = [];
|
||||
for (const [roomId, userSet] of rooms.entries()) {
|
||||
roomList.push({
|
||||
roomId,
|
||||
userCount: userSet.size,
|
||||
users: getRoomUsers(roomId).map(u => ({
|
||||
nickname: u.nickname,
|
||||
joinedAt: u.joinedAt
|
||||
}))
|
||||
});
|
||||
}
|
||||
res.json(roomList);
|
||||
});
|
||||
|
||||
app.get('/api/rooms/:roomId', (req, res) => {
|
||||
const { roomId } = req.params;
|
||||
if (rooms.has(roomId)) {
|
||||
res.json({
|
||||
roomId,
|
||||
userCount: rooms.get(roomId).size,
|
||||
users: getRoomUsers(roomId)
|
||||
});
|
||||
} else {
|
||||
res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
});
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Serve a simple test page
|
||||
app.get('/', (req, res) => {
|
||||
res.send(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ChatRTC Signaling Server</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
|
||||
.container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
h1 { color: #2196F3; }
|
||||
.status { background: #e8f5e8; padding: 15px; border-radius: 5px; margin: 20px 0; }
|
||||
.endpoint { background: #f8f9fa; padding: 10px; margin: 10px 0; border-left: 4px solid #2196F3; }
|
||||
code { background: #f0f0f0; padding: 2px 5px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 ChatRTC Signaling Server</h1>
|
||||
<div class="status">
|
||||
<strong>✅ Server is running successfully!</strong><br>
|
||||
Connected Users: <span id="users">${users.size}</span><br>
|
||||
Active Rooms: <span id="rooms">${rooms.size}</span><br>
|
||||
Uptime: <span id="uptime">${Math.floor(process.uptime())}s</span>
|
||||
</div>
|
||||
|
||||
<h2>📡 API Endpoints</h2>
|
||||
<div class="endpoint">
|
||||
<strong>GET /api/status</strong> - Server status and statistics
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>GET /api/rooms</strong> - List all active rooms
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>GET /api/rooms/:roomId</strong> - Get specific room info
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>GET /health</strong> - Health check endpoint
|
||||
</div>
|
||||
|
||||
<h2>🔧 Socket.IO Events</h2>
|
||||
<p>The server handles the following Socket.IO events for WebRTC signaling:</p>
|
||||
<ul>
|
||||
<li><code>join-room</code> - Join a chat room</li>
|
||||
<li><code>offer</code> - WebRTC offer signaling</li>
|
||||
<li><code>answer</code> - WebRTC answer signaling</li>
|
||||
<li><code>ice-candidate</code> - ICE candidate exchange</li>
|
||||
<li><code>media-state</code> - Video/audio state updates</li>
|
||||
<li><code>chat-message</code> - Text message fallback</li>
|
||||
</ul>
|
||||
|
||||
<h2>📱 Android App Connection</h2>
|
||||
<p>Update your Android app's WebRTCManager.java with this server URL:</p>
|
||||
<div class="endpoint">
|
||||
<code>private static final String SIGNALING_SERVER_URL = "http://YOUR_SERVER_IP:${process.env.PORT || 3000}";</code>
|
||||
</div>
|
||||
|
||||
<p><em>Server started at: ${new Date().toISOString()}</em></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-refresh stats every 5 seconds
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/status');
|
||||
const data = await response.json();
|
||||
document.getElementById('users').textContent = data.connectedUsers;
|
||||
document.getElementById('rooms').textContent = data.activeRooms;
|
||||
document.getElementById('uptime').textContent = Math.floor(data.uptime) + 's';
|
||||
} catch (error) {
|
||||
console.error('Failed to update stats:', error);
|
||||
}
|
||||
}, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
});
|
||||
|
||||
// Error handling
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Express error:', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SIGTERM received, shutting down gracefully');
|
||||
server.close(() => {
|
||||
console.log('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('SIGINT received, shutting down gracefully');
|
||||
server.close(() => {
|
||||
console.log('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log('🚀 ChatRTC Signaling Server Started');
|
||||
console.log(`📡 Server running on http://${HOST}:${PORT}`);
|
||||
console.log(`🌐 Socket.IO enabled with CORS`);
|
||||
console.log(`📱 Ready for Android app connections`);
|
||||
console.log(`⏰ Started at: ${new Date().toISOString()}`);
|
||||
});
|
||||
|
||||
module.exports = { app, server, io };
|
||||
222
server/start-server.sh
Archivo ejecutable
222
server/start-server.sh
Archivo ejecutable
@@ -0,0 +1,222 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ChatRTC Signaling Server Startup Script
|
||||
|
||||
set -e
|
||||
|
||||
SERVER_DIR="/home/ale/projects/android/chatrtc/server"
|
||||
LOG_FILE="$SERVER_DIR/logs/startup.log"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
mkdir -p "$SERVER_DIR/logs"
|
||||
|
||||
echo -e "${BLUE}🚀 ChatRTC Signaling Server${NC}"
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
|
||||
# Function to print colored messages
|
||||
print_status() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
# Check if Node.js is installed
|
||||
if ! command -v node &> /dev/null; then
|
||||
print_error "Node.js is not installed. Please install Node.js 16 or later."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node --version | cut -d'.' -f1 | cut -d'v' -f2)
|
||||
if [ "$NODE_VERSION" -lt 16 ]; then
|
||||
print_warning "Node.js version is $NODE_VERSION. Recommended version is 16 or later."
|
||||
fi
|
||||
|
||||
# Check if npm is installed
|
||||
if ! command -v npm &> /dev/null; then
|
||||
print_error "npm is not installed. Please install npm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Change to server directory
|
||||
cd "$SERVER_DIR"
|
||||
|
||||
print_info "Working directory: $SERVER_DIR"
|
||||
|
||||
# Check if package.json exists
|
||||
if [ ! -f "package.json" ]; then
|
||||
print_error "package.json not found. Make sure you're in the correct directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install dependencies if node_modules doesn't exist
|
||||
if [ ! -d "node_modules" ]; then
|
||||
print_info "Installing dependencies..."
|
||||
npm install
|
||||
print_status "Dependencies installed"
|
||||
else
|
||||
print_status "Dependencies already installed"
|
||||
fi
|
||||
|
||||
# Create .env file if it doesn't exist
|
||||
if [ ! -f ".env" ]; then
|
||||
print_info "Creating .env file from template..."
|
||||
cp .env.example .env
|
||||
print_status ".env file created"
|
||||
fi
|
||||
|
||||
# Get the local IP address
|
||||
if command -v hostname &> /dev/null; then
|
||||
LOCAL_IP=$(hostname -I | awk '{print $1}')
|
||||
else
|
||||
LOCAL_IP="localhost"
|
||||
fi
|
||||
|
||||
# Function to start server
|
||||
start_server() {
|
||||
local MODE=$1
|
||||
|
||||
print_info "Starting server in $MODE mode..."
|
||||
|
||||
case $MODE in
|
||||
"dev")
|
||||
npm run dev
|
||||
;;
|
||||
"prod")
|
||||
npm start
|
||||
;;
|
||||
"pm2")
|
||||
if ! command -v pm2 &> /dev/null; then
|
||||
print_warning "PM2 not installed. Installing PM2..."
|
||||
npm install -g pm2
|
||||
fi
|
||||
npm run pm2:start
|
||||
print_status "Server started with PM2"
|
||||
echo
|
||||
print_info "Useful PM2 commands:"
|
||||
echo " pm2 status - Check server status"
|
||||
echo " pm2 logs chatrtc-server - View logs"
|
||||
echo " pm2 restart chatrtc-server - Restart server"
|
||||
echo " pm2 stop chatrtc-server - Stop server"
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid mode. Use: dev, prod, or pm2"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to test server
|
||||
test_server() {
|
||||
local SERVER_URL=${1:-"http://localhost:3000"}
|
||||
|
||||
print_info "Testing server at $SERVER_URL..."
|
||||
|
||||
if [ -f "test-client.js" ]; then
|
||||
node test-client.js "$SERVER_URL"
|
||||
else
|
||||
print_warning "test-client.js not found. Skipping server test."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to show server info
|
||||
show_info() {
|
||||
local PORT=${PORT:-3000}
|
||||
|
||||
echo
|
||||
print_status "Server Information:"
|
||||
echo " 📍 Local URL: http://localhost:$PORT"
|
||||
echo " 🌐 Network URL: http://$LOCAL_IP:$PORT"
|
||||
echo " 📱 Android Emulator URL: http://10.0.2.2:$PORT"
|
||||
echo
|
||||
print_info "Update your Android app's WebRTCManager.java:"
|
||||
echo " private static final String SIGNALING_SERVER_URL = \"http://$LOCAL_IP:$PORT\";"
|
||||
echo
|
||||
print_info "Available endpoints:"
|
||||
echo " GET / - Server status page"
|
||||
echo " GET /api/status - Server statistics"
|
||||
echo " GET /api/rooms - Active rooms"
|
||||
echo " GET /health - Health check"
|
||||
echo
|
||||
}
|
||||
|
||||
# Function to show usage
|
||||
show_usage() {
|
||||
echo "Usage: $0 [COMMAND] [OPTIONS]"
|
||||
echo
|
||||
echo "Commands:"
|
||||
echo " start [dev|prod|pm2] - Start the server (default: dev)"
|
||||
echo " test [URL] - Test server connection"
|
||||
echo " info - Show server information"
|
||||
echo " stop - Stop PM2 server"
|
||||
echo " logs - Show PM2 logs"
|
||||
echo " status - Show PM2 status"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 start dev - Start in development mode"
|
||||
echo " $0 start pm2 - Start with PM2 process manager"
|
||||
echo " $0 test - Test local server"
|
||||
echo " $0 info - Show connection information"
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
case ${1:-start} in
|
||||
"start")
|
||||
MODE=${2:-dev}
|
||||
show_info
|
||||
start_server "$MODE"
|
||||
;;
|
||||
"test")
|
||||
test_server "$2"
|
||||
;;
|
||||
"info")
|
||||
show_info
|
||||
;;
|
||||
"stop")
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 stop chatrtc-server
|
||||
print_status "Server stopped"
|
||||
else
|
||||
print_error "PM2 not installed"
|
||||
fi
|
||||
;;
|
||||
"logs")
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 logs chatrtc-server
|
||||
else
|
||||
print_error "PM2 not installed"
|
||||
fi
|
||||
;;
|
||||
"status")
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 status chatrtc-server
|
||||
else
|
||||
print_error "PM2 not installed"
|
||||
fi
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
show_usage
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown command: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
195
server/test-client.js
Archivo ejecutable
195
server/test-client.js
Archivo ejecutable
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Simple test client for ChatRTC signaling server
|
||||
const io = require('socket.io-client');
|
||||
|
||||
const SERVER_URL = process.argv[2] || 'http://localhost:3000';
|
||||
const NICKNAME = process.argv[3] || `TestUser_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
console.log(`🧪 Testing ChatRTC Signaling Server`);
|
||||
console.log(`📡 Connecting to: ${SERVER_URL}`);
|
||||
console.log(`👤 Nickname: ${NICKNAME}`);
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
const socket = io(SERVER_URL, {
|
||||
transports: ['websocket', 'polling']
|
||||
});
|
||||
|
||||
// Connection events
|
||||
socket.on('connect', () => {
|
||||
console.log('✅ Connected to server');
|
||||
console.log(`🔗 Socket ID: ${socket.id}`);
|
||||
|
||||
// Join room after connection
|
||||
setTimeout(() => {
|
||||
socket.emit('join-room', {
|
||||
nickname: NICKNAME,
|
||||
roomId: 'test-room'
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log(`❌ Disconnected: ${reason}`);
|
||||
});
|
||||
|
||||
socket.on('connect_error', (error) => {
|
||||
console.error('❌ Connection error:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Room events
|
||||
socket.on('joined-room', (data) => {
|
||||
console.log('🏠 Successfully joined room:');
|
||||
console.log(` Room ID: ${data.roomId}`);
|
||||
console.log(` Nickname: ${data.nickname}`);
|
||||
console.log(` Users in room: ${data.users.length}`);
|
||||
|
||||
// Send a test message after joining
|
||||
setTimeout(() => {
|
||||
socket.emit('chat-message', {
|
||||
message: `Hello from ${NICKNAME}! 👋`
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
socket.on('user-joined', (data) => {
|
||||
console.log(`👋 User joined: ${data.nickname} (${data.socketId})`);
|
||||
});
|
||||
|
||||
socket.on('user-left', (data) => {
|
||||
console.log(`👋 User left: ${data.nickname} (${data.socketId})`);
|
||||
});
|
||||
|
||||
// Message events
|
||||
socket.on('chat-message', (data) => {
|
||||
console.log(`💬 Message from ${data.fromNickname}: ${data.message}`);
|
||||
});
|
||||
|
||||
// WebRTC signaling events
|
||||
socket.on('offer', (data) => {
|
||||
console.log(`📞 Received offer from ${data.fromNickname}`);
|
||||
|
||||
// Send back a mock answer
|
||||
setTimeout(() => {
|
||||
socket.emit('answer', {
|
||||
targetSocketId: data.fromSocketId,
|
||||
answer: {
|
||||
type: 'answer',
|
||||
sdp: 'mock-answer-sdp-for-testing'
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
socket.on('answer', (data) => {
|
||||
console.log(`📞 Received answer from ${data.fromNickname}`);
|
||||
});
|
||||
|
||||
socket.on('ice-candidate', (data) => {
|
||||
console.log(`🧊 Received ICE candidate from ${data.fromNickname}`);
|
||||
});
|
||||
|
||||
// Error handling
|
||||
socket.on('error', (data) => {
|
||||
console.error(`❌ Server error: ${data.message}`);
|
||||
if (data.code) {
|
||||
console.error(` Error code: ${data.code}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Test sequence
|
||||
let testStep = 0;
|
||||
const runTests = () => {
|
||||
testStep++;
|
||||
|
||||
switch (testStep) {
|
||||
case 1:
|
||||
console.log('🧪 Test 1: Sending ping...');
|
||||
socket.emit('ping');
|
||||
break;
|
||||
|
||||
case 2:
|
||||
console.log('🧪 Test 2: Requesting room list...');
|
||||
socket.emit('get-rooms');
|
||||
break;
|
||||
|
||||
case 3:
|
||||
console.log('🧪 Test 3: Requesting room users...');
|
||||
socket.emit('get-room-users');
|
||||
break;
|
||||
|
||||
case 4:
|
||||
console.log('🧪 Test 4: Sending mock WebRTC offer...');
|
||||
socket.emit('offer', {
|
||||
offer: {
|
||||
type: 'offer',
|
||||
sdp: 'mock-offer-sdp-for-testing'
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 5:
|
||||
console.log('🧪 Test 5: Updating media state...');
|
||||
socket.emit('media-state', {
|
||||
isVideoEnabled: false,
|
||||
isAudioEnabled: true
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('✅ All tests completed!');
|
||||
setTimeout(() => {
|
||||
console.log('👋 Disconnecting...');
|
||||
socket.disconnect();
|
||||
process.exit(0);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(runTests, 2000);
|
||||
};
|
||||
|
||||
// Additional event handlers for tests
|
||||
socket.on('pong', (data) => {
|
||||
console.log(`🏓 Pong received (${data.timestamp})`);
|
||||
});
|
||||
|
||||
socket.on('rooms-list', (rooms) => {
|
||||
console.log(`🏠 Rooms list received (${rooms.length} rooms):`);
|
||||
rooms.forEach(room => {
|
||||
console.log(` - ${room.roomId}: ${room.userCount} users`);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('room-users', (data) => {
|
||||
console.log(`👥 Room users for ${data.roomId} (${data.users.length} users):`);
|
||||
data.users.forEach(user => {
|
||||
console.log(` - ${user.nickname} (joined: ${user.joinedAt})`);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('user-media-state', (data) => {
|
||||
console.log(`📹 Media state update from ${data.nickname}: Video=${data.isVideoEnabled}, Audio=${data.isAudioEnabled}`);
|
||||
});
|
||||
|
||||
// Start tests after successful room join
|
||||
socket.on('joined-room', () => {
|
||||
setTimeout(() => {
|
||||
console.log('🧪 Starting test sequence...');
|
||||
runTests();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n👋 Shutting down test client...');
|
||||
socket.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('\n👋 Shutting down test client...');
|
||||
socket.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
Referencia en una nueva incidencia
Block a user