[mirotalksfu] - Improve config.js.template, add .env.template, update dep
هذا الالتزام موجود في:
335
.env.template
Normal file
335
.env.template
Normal file
@@ -0,0 +1,335 @@
|
||||
# ========================================================
|
||||
# MiroTalk SFU - Environment Configuration
|
||||
# ========================================================
|
||||
|
||||
# config.js - Main configuration with:
|
||||
# - All default values and documentation
|
||||
# - Complex logic and validations
|
||||
# - Safe to commit to version control
|
||||
#
|
||||
# .env - Environment overrides with:
|
||||
# - Secrets and sensitive data (NEVER commit)
|
||||
# - Environment-specific settings
|
||||
# - Simple key=value format
|
||||
#
|
||||
# Why this works best:
|
||||
# 1. SECURITY: Secrets stay out of codebase
|
||||
# 2. FLEXIBILITY: Change settings without redeploy
|
||||
# 3. SAFETY: Built-in defaults prevent crashes
|
||||
# 4. DOCS: config.js explains all options
|
||||
#
|
||||
# Best practice:
|
||||
# - Keep DEFAULT values in config.js
|
||||
# - Keep SECRETS/ENV-SPECIFIC in .env
|
||||
# - Add .env to .gitignore
|
||||
# ==============================================
|
||||
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 1. Core System Configuration
|
||||
# ----------------------------------------------------
|
||||
|
||||
NODE_ENV=development # Runtime environment: development|production
|
||||
SFU_PUBLIC_IP= # Public IP address for WebRTC announcements
|
||||
SFU_LISTEN_IP=0.0.0.0 # IP address to bind to
|
||||
SFU_MIN_PORT=40000 # Minimum WebRTC port range
|
||||
SFU_MAX_PORT=40100 # Maximum WebRTC port range
|
||||
SFU_NUM_WORKERS= # Number of worker processes (defaults to CPU count)
|
||||
SFU_SERVER=false # Enable/disable WebRTC server (true|false)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. Server Configuration
|
||||
# ----------------------------------------------------
|
||||
|
||||
SERVER_HOST_URL= # Public server URL (e.g., https://yourdomain.com)
|
||||
SERVER_LISTEN_IP=0.0.0.0 # Server bind IP
|
||||
SERVER_LISTEN_PORT=3010 # Port to listen on
|
||||
TRUST_PROXY=false # Trust proxy headers (true in production behind reverse proxy)
|
||||
SERVER_SSL_CERT=../ssl/cert.pem # Path to SSL certificate
|
||||
SERVER_SSL_KEY=../ssl/key.pem # Path to SSL private key
|
||||
CORS_ORIGIN=* # Allowed CORS origins (comma-separated)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 3. Media Handling
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Recording
|
||||
RECORDING_ENABLED=false # Enable recording functionality (true|false)
|
||||
RECORDING_ENDPOINT= # Recording service endpoint es http://localhost:8080
|
||||
|
||||
# Rtmp streaming
|
||||
RTMP_ENABLED=true # Enable RTMP streaming (true|false)
|
||||
RTMP_FROM_FILE=true # Enable local file streaming
|
||||
RTMP_FROM_URL=true # Enable URL streaming
|
||||
RTMP_FROM_STREAM=true # Enable live stream (camera, microphone, screen, window)
|
||||
RTMP_MAX_STREAMS=1 # Max simultaneous RTMP streams
|
||||
RTMP_SERVER=rtmp://localhost:1935 # RTMP server URL
|
||||
RTMP_APP_NAME=mirotalk # RTMP application name
|
||||
RTMP_STREAM_KEY= # RTMP stream key (optional)
|
||||
RTMP_SECRET=mirotalkRtmpSecret # RTMP API secret
|
||||
RTMP_API_SECRET=mirotalkRtmpApiSecret # RTMP internal secret
|
||||
RTMP_EXPIRATION_HOURS=4 # RTMP URL validity duration (hours)
|
||||
RTMP_FFMPEG_PATH= # RTMP Custom path to FFmpeg binary (auto-detected if empty)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 4. Security & Authentication
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Middleware
|
||||
IP_WHITELIST_ENABLED=false # Restrict access by IP (true|false)
|
||||
IP_WHITELIST_ALLOWED=127.0.0.1,::1 # Allowed IPs (comma-separated)
|
||||
|
||||
# Token
|
||||
JWT_SECRET=mirotalksfu_jwt_secret # Secret for JWT tokens
|
||||
JWT_EXPIRATION=1h # JWT token expiration (e.g., 1h, 7d)
|
||||
|
||||
# OIDC
|
||||
OIDC_ENABLED=false # Enable OpenID Connect (true|false)
|
||||
OIDC_ISSUER=https://server.example.com # OIDC provider URL
|
||||
OIDC_BASE_URL= # OIDC base URL es https://yourdomain.com
|
||||
OIDC_CLIENT_ID=clientID # OIDC client ID
|
||||
OIDC_CLIENT_SECRET=clientSecret # OIDC client secret
|
||||
OIDC_SECRET=mirotalksfu-oidc-secret # OIDC secret
|
||||
|
||||
# Host protection
|
||||
HOST_PROTECTED=false # Enable host protection (true|false)
|
||||
HOST_USER_AUTH=false # Enable user authentication (true|false)
|
||||
|
||||
# Endpoints
|
||||
HOST_USERS_FROM_DB=false # Use DB for user auth (true|false)
|
||||
USERS_API_SECRET=mirotalkweb_default_secret # Users API secret key
|
||||
USERS_API_ENDPOINT=http://localhost:9000/api/v1/user/isAuth # User auth endpoint
|
||||
USERS_ROOM_ALLOWED_ENDPOINT=http://localhost:9000/api/v1/user/isRoomAllowed # Room permission endpoint
|
||||
USERS_ROOMS_ALLOWED_ENDPOINT=http://localhost:9000/api/v1/user/roomsAllowed # Allowed rooms endpoint
|
||||
ROOM_EXISTS_ENDPOINT=http://localhost:9000/api/v1/room/exists # Room exists endpoint
|
||||
|
||||
# Users
|
||||
DEFAULT_USERNAME=username # Default admin username
|
||||
DEFAULT_PASSWORD=password # Default admin password
|
||||
DEFAULT_DISPLAY_NAME=username display name # Default display name
|
||||
DEFAULT_ALLOWED_ROOMS=* # Default allowed rooms all or room1,room2... (comma-separated)
|
||||
|
||||
# Presenters
|
||||
PRESENTERS=Miroslav Pejic,miroslav.pejic.85@gmail.com, # Presenter usernames (comma-separated)
|
||||
PRESENTER_JOIN_FIRST=true # First joiner becomes presenter (true|false)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 5. API Configuration
|
||||
# ----------------------------------------------------
|
||||
|
||||
API_SECRET=mirotalksfu_default_secret # Secret for API authentication
|
||||
API_ALLOW_STATS=true # Enable stats API (true|false)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 6. Third-Party Integrations
|
||||
# ----------------------------------------------------
|
||||
|
||||
# ChatGPT Integration
|
||||
CHATGPT_ENABLED=false # Enable ChatGPT integration (true|false)
|
||||
CHATGPT_BASE_PATH=https://api.openai.com/v1/ # OpenAI base path
|
||||
CHATGPT_API_KEY= # OpenAI API key
|
||||
CHATGPT_MODEL=gpt-3.5-turbo # Model to use (gpt-3.5-turbo, gpt-4, etc.)
|
||||
CHATGPT_MAX_TOKENS=1000 # Max response tokens
|
||||
CHATGPT_TEMPERATURE=0 # Creativity level (0-1)
|
||||
|
||||
# Video AI (HeyGen) Integration
|
||||
VIDEOAI_ENABLED=false # Enable video AI avatars (true|false)
|
||||
VIDEOAI_API_KEY= # HeyGen API key
|
||||
VIDEOAI_SYSTEM_LIMIT= # AI system prompt
|
||||
|
||||
# Email Alerts
|
||||
EMAIL_ALERTS_ENABLED=false # Enable email notifications (true|false)
|
||||
EMAIL_HOST=smtp.gmail.com # SMTP server host
|
||||
EMAIL_PORT=587 # SMTP port
|
||||
EMAIL_USERNAME=your_username # SMTP username
|
||||
EMAIL_PASSWORD=your_password # SMTP password
|
||||
EMAIL_SEND_TO=sfu.mirotalk@gmail.com # Notification recipient
|
||||
|
||||
# Slack Integration
|
||||
SLACK_ENABLED=false # Enable Slack integration (true|false)
|
||||
SLACK_SIGNING_SECRET= # Slack app signing secret
|
||||
|
||||
# Mattermost Integration
|
||||
MATTERMOST_ENABLED=false # Enable Mattermost (true|false)
|
||||
MATTERMOST_SERVER_URL=YourMattermostServerUrl # Mattermost server URL
|
||||
MATTERMOST_USERNAME=YourMattermostUsername # Mattermost username
|
||||
MATTERMOST_PASSWORD=YourMattermostPassword # Mattermost password
|
||||
MATTERMOST_TOKEN=YourMattermostToken # Mattermost slash command token
|
||||
MATTERMOST_COMMAND_NAME=/sfu # Mattermost command name
|
||||
MATTERMOST_DEFAULT_MESSAGE=Here is your meeting room: # Mattermost default message
|
||||
|
||||
# Discord Integration
|
||||
DISCORD_ENABLED=false # Enable Discord bot (true|false)
|
||||
DISCORD_TOKEN= # Discord bot token
|
||||
DISCORD_COMMAND_NAME=/sfu # Discord command name
|
||||
DISCORD_DEFAULT_MESSAGE=Here is your SFU meeting room: # Discord default message
|
||||
DISCORD_BASE_URL=https://sfu.mirotalk.com/join/ # Discord Base URL for meeting rooms
|
||||
|
||||
# Ngrok Tunnel
|
||||
NGROK_ENABLED=false # Enable Ngrok tunneling (true|false)
|
||||
NGROK_AUTH_TOKEN= # Ngrok authentication token
|
||||
|
||||
# Sentry Error Tracking
|
||||
SENTRY_ENABLED=false # Enable Sentry error tracking (true|false)
|
||||
SENTRY_DSN= # Sentry DSN URL
|
||||
SENTRY_TRACES_SAMPLE_RATE=0.2 # Error sampling rate (0-1)
|
||||
|
||||
# Webhook Notifications
|
||||
WEBHOOK_ENABLED=false # Enable webhook notifications (true|false)
|
||||
WEBHOOK_URL=https://your-site.com/webhook-endpoint # Webhook endpoint URL
|
||||
|
||||
# IP Geolocation
|
||||
IP_LOOKUP_ENABLED=false # Enable IP lookup functionality (true|false)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 7. UI Customization
|
||||
# ----------------------------------------------------
|
||||
|
||||
# App
|
||||
UI_LANGUAGE=en # Interface language (en, es, fr, etc.)
|
||||
APP_NAME=MiroTalk SFU # Application name
|
||||
APP_TITLE= # Custom HTML title (leave empty for default)
|
||||
APP_DESCRIPTION= # Application description
|
||||
JOIN_DESCRIPTION= # Join screen description
|
||||
JOIN_BUTTON_LABEL=JOIN ROOM # Join button text
|
||||
JOIN_LAST_LABEL=Your recent room: # Recent room label text
|
||||
|
||||
# Site
|
||||
SITE_TITLE= # Website title
|
||||
SITE_ICON_PATH=../images/logo.svg # Favicon path
|
||||
APPLE_TOUCH_ICON_PATH=../images/logo.svg # Apple touch icon path
|
||||
NEW_ROOM_TITLE= # New room title
|
||||
NEW_ROOM_DESC= # New room description
|
||||
|
||||
# Meta
|
||||
META_DESCRIPTION= # HTML meta description
|
||||
META_KEYWORDS= # HTML meta keywords
|
||||
|
||||
# OG
|
||||
OG_TYPE=app-webrtc # OpenGraph type
|
||||
OG_SITE_NAME=MiroTalk SFU # OG site name
|
||||
OG_TITLE= # OG title
|
||||
OG_DESCRIPTION= # OG description
|
||||
OG_IMAGE_URL=https://sfu.mirotalk.com/images/mirotalksfu.png # OG image
|
||||
OG_URL=https://sfu.mirotalk.com # OG URL
|
||||
|
||||
# HTML
|
||||
SHOW_FEATURES=true # Show features section (true|false)
|
||||
SHOW_TEAMS=true # Show teams section (true|false)
|
||||
SHOW_TRY_EASIER=true # Show "try easier" section (true|false)
|
||||
SHOW_POWERED_BY=true # Show powered by (true|false)
|
||||
SHOW_SPONSORS=true # Show sponsors (true|false)
|
||||
SHOW_ADVERTISERS=true # Show advertisers (true|false)
|
||||
SHOW_FOOTER=true # Show footer (true|false)
|
||||
|
||||
# About
|
||||
ABOUT_IMAGE_URL=../images/mirotalk-logo.gif # About section image
|
||||
SUPPORT_URL=https://codecanyon.net/user/miroslavpejic85 # Support link URL
|
||||
SUPPORT_TEXT=Support # Support button text
|
||||
AUTHOR_LABEL=Author # Author label text
|
||||
AUTHOR_NAME=Miroslav Pejic # Author name
|
||||
LINKEDIN_URL=https://www.linkedin.com/in/miroslav-pejic-976a07101/ # LinkedIn profile
|
||||
CONTACT_EMAIL=miroslav.pejic.85@gmail.com # Contact email
|
||||
EMAIL_SUBJECT=MiroTalk SFU info # Email subject
|
||||
COPYRIGHT_TEXT=MiroTalk SFU, all rights reserved # Copyright text
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 8. UI Button Configuration
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Main Control Buttons
|
||||
SHOW_SHARE_BUTTON=true # Show share button (true|false)
|
||||
SHOW_HIDE_ME=true # Show hide me button (true|false)
|
||||
SHOW_AUDIO_BUTTON=true # Show audio button (true|false)
|
||||
SHOW_VIDEO_BUTTON=true # Show video button (true|false)
|
||||
SHOW_SCREEN_BUTTON=true # Show screen share button (true|false)
|
||||
SHOW_SWAP_CAMERA=true # Show camera swap button (true|false)
|
||||
SHOW_CHAT_BUTTON=true # Show chat button (true|false)
|
||||
SHOW_POLL_BUTTON=true # Show poll button (true|false)
|
||||
SHOW_EDITOR_BUTTON=true # Show editor button (true|false)
|
||||
SHOW_RAISE_HAND=true # Show raise hand button (true|false)
|
||||
SHOW_TRANSCRIPTION=true # Show transcription button (true|false)
|
||||
SHOW_WHITEBOARD=true # Show whiteboard button (true|false)
|
||||
SHOW_DOCUMENT_PIP=true # Show document PiP button (true|false)
|
||||
SHOW_SNAPSHOT=true # Show snapshot button (true|false)
|
||||
SHOW_EMOJI=true # Show emoji button (true|false)
|
||||
SHOW_SETTINGS=true # Show settings button (true|false)
|
||||
SHOW_ABOUT=true # Show about button (true|false)
|
||||
SHOW_EXIT_BUTTON=true # Show exit button (true|false)
|
||||
|
||||
# Settings Panel
|
||||
ENABLE_FILE_SHARING=true # Enable file sharing (true|false)
|
||||
SHOW_LOCK_ROOM=true # Show lock room button (true|false)
|
||||
SHOW_UNLOCK_ROOM=true # Show unlock room button (true|false)
|
||||
SHOW_BROADCASTING=true # Show broadcasting button (true|false)
|
||||
SHOW_LOBBY=true # Show lobby button (true|false)
|
||||
SHOW_EMAIL_INVITE=true # Show email invitation button (true|false)
|
||||
SHOW_MIC_OPTIONS=true # Show mic options button (true|false)
|
||||
SHOW_RTMP_TAB=true # Show RTMP tab (true|false)
|
||||
SHOW_MODERATOR_TAB=true # Show moderator tab (true|false)
|
||||
SHOW_RECORDING_TAB=true # Show recording tab (true|false)
|
||||
HOST_ONLY_RECORDING=true # Only host can record (true|false)
|
||||
ENABLE_PUSH_TO_TALK=true # Enable push-to-talk (true|false)
|
||||
SHOW_KEYBOARD_SHORTCUTS=true # Show keyboard shortcuts (true|false)
|
||||
SHOW_VIRTUAL_BACKGROUND=true # Show virtual background (true|false)
|
||||
|
||||
# Video Controls
|
||||
ENABLE_PIP=true # Enable picture-in-picture (true|false)
|
||||
SHOW_MIRROR_BUTTON=true # Show mirror button (true|false)
|
||||
SHOW_FULLSCREEN=true # Show fullscreen button (true|false)
|
||||
SHOW_SNAPSHOT_BUTTON=true # Show snapshot button (true|false)
|
||||
SHOW_MUTE_AUDIO=true # Show mute audio button (true|false)
|
||||
SHOW_PRIVACY_TOGGLE=true # Show privacy toggle (true|false)
|
||||
SHOW_VOLUME_CONTROL=true # Show volume control (true|false)
|
||||
SHOW_FOCUS_BUTTON=true # Show focus button (true|false)
|
||||
SHOW_SEND_MESSAGE=true # Show send message button (true|false)
|
||||
SHOW_SEND_FILE=true # Show send file button (true|false)
|
||||
SHOW_SEND_VIDEO=true # Show send video button (true|false)
|
||||
SHOW_MUTE_VIDEO=true # Show mute video button (true|false)
|
||||
SHOW_GEO_LOCATION=true # Show geoLocation button (true|false)
|
||||
SHOW_BAN_BUTTON=true # Show ban button (true|false)
|
||||
SHOW_EJECT_BUTTON=true # Show eject button (true|false)
|
||||
|
||||
# Chat Controls
|
||||
SHOW_CHAT_PIN=true # Show chat pin button (true|false)
|
||||
SHOW_CHAT_MAXIMIZE=true # Show chat maximize button (true|false)
|
||||
SHOW_CHAT_SAVE=true # Show chat save button (true|false)
|
||||
SHOW_CHAT_EMOJI=true # Show chat emoji button (true|false)
|
||||
SHOW_CHAT_MARKDOWN=true # Show chat markdown button (true|false)
|
||||
SHOW_CHAT_SPEECH=true # Show chat speech button (true|false)
|
||||
ENABLE_CHAT_GPT=true # Enable ChatGPT in chat (true|false)
|
||||
|
||||
# Poll Controls
|
||||
SHOW_POLL_PIN=true # Show poll pin button (true|false)
|
||||
SHOW_POLL_MAXIMIZE=true # Show poll maximize button (true|false)
|
||||
SHOW_POLL_SAVE=true # Show poll save button (true|false)
|
||||
|
||||
# Participants Controls
|
||||
SHOW_SAVE_INFO=true # Show save info button (true|false)
|
||||
SHOW_SEND_FILE_ALL=true # Show send file to all button (true|false)
|
||||
SHOW_EJECT_ALL=true # Show eject all button (true|false)
|
||||
|
||||
# Whiteboard Controls
|
||||
SHOW_WB_LOCK=true # Show whiteboard lock button (true|false)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 9. Feature Flags
|
||||
# ----------------------------------------------------
|
||||
|
||||
SURVEY_ENABLED=false # Enable post-call survey (true|false)
|
||||
SURVEY_URL= # Survey URL
|
||||
|
||||
# Redirect
|
||||
REDIRECT_ENABLED=false # Enable post-call redirect (true|false)
|
||||
REDIRECT_URL= # Redirect URL
|
||||
|
||||
# Stats
|
||||
STATS_ENABLED=true # Enable usage statistics (true|false)
|
||||
STATS_SRC=https://stats.mirotalk.com/script.js # Stats script URL
|
||||
STATS_ID=41d26670-f275-45bb-af82-3ce91fe57756 # Stats tracking ID
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 10. Mediasoup Configuration
|
||||
# ----------------------------------------------------
|
||||
|
||||
MEDIASOUP_LOG_LEVEL=error # Mediasoup log level (debug, warn, error)
|
||||
16
README.md
16
README.md
@@ -204,12 +204,14 @@ $ git clone https://github.com/miroslavpejic85/mirotalksfu.git
|
||||
$ cd mirotalksfu
|
||||
# Copy app/src/config.template.js in app/src/config.js and edit it if needed
|
||||
$ cp app/src/config.template.js app/src/config.js
|
||||
# Copy .env.template to .env and edit it if needed
|
||||
$ cp .env.template .env
|
||||
# Install dependencies - be patient, the first time will take a few minutes, in the meantime have a good coffee ;)
|
||||
$ npm install
|
||||
# Start the server
|
||||
$ npm start
|
||||
# If you want to start the server on a different port than the default use an env var
|
||||
$ PORT=3011 npm start
|
||||
$ SERVER_LISTEN_PORT=3011 npm start
|
||||
```
|
||||
|
||||
- Open [https://localhost:3010](https://localhost:3010) or `:3011` if the default port has been changed in your browser.
|
||||
@@ -239,10 +241,22 @@ $ git clone https://github.com/miroslavpejic85/mirotalksfu.git
|
||||
$ cd mirotalksfu
|
||||
# Copy app/src/config.template.js in app/src/config.js IMPORTANT (edit it according to your needs)
|
||||
$ cp app/src/config.template.js app/src/config.js
|
||||
# Copy .env.template to .env and edit it if needed
|
||||
$ cp .env.template .env
|
||||
# Copy docker-compose.template.yml in docker-compose.yml and edit it if needed
|
||||
$ cp docker-compose.template.yml docker-compose.yml
|
||||
# (Optional) Get official image from Docker Hub
|
||||
$ docker-compose pull
|
||||
```
|
||||
|
||||
Make sure to load the `.env` file in your `docker-compose.yml` by adding:
|
||||
|
||||
```yaml
|
||||
env_file:
|
||||
- .env
|
||||
```
|
||||
|
||||
```bash
|
||||
# Create and start containers
|
||||
$ docker-compose up # -d
|
||||
# To stop and remove resources
|
||||
|
||||
@@ -6,21 +6,21 @@ const colors = require('colors');
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
config.console.colors ? colors.enable() : colors.disable();
|
||||
config.system?.console?.colors ? colors.enable() : colors.disable();
|
||||
|
||||
const options = {
|
||||
depth: null,
|
||||
colors: config.console.colors,
|
||||
colors: config.system?.console?.colors || false,
|
||||
};
|
||||
module.exports = class Logger {
|
||||
constructor(appName = 'miroTalkSfu') {
|
||||
this.appName = colors.yellow(appName);
|
||||
this.debugOn = config.console.debug;
|
||||
this.debugOn = config.system?.console?.debug || true;
|
||||
this.timeStart = Date.now();
|
||||
this.timeEnd = null;
|
||||
this.timeElapsedMs = null;
|
||||
this.tzOptions = {
|
||||
timeZone: process.env.TZ || config.console.timeZone || 'UTC',
|
||||
timeZone: process.env.TZ || config.system?.console?.timeZone || 'UTC',
|
||||
hour12: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ class Mattermost {
|
||||
password,
|
||||
commands = '/sfu',
|
||||
texts = '/sfu',
|
||||
} = config.mattermost || {};
|
||||
} = config.integrations.mattermost || {};
|
||||
|
||||
if (!enabled) return; // Check if Mattermost integration is enabled
|
||||
|
||||
this.app = app;
|
||||
this.allowed = config.api.allowed && config.api.allowed.mattermost;
|
||||
this.allowed = config.api?.allowed?.mattermost || false;
|
||||
this.token = token;
|
||||
this.serverUrl = serverUrl;
|
||||
this.username = username;
|
||||
|
||||
@@ -34,8 +34,8 @@ module.exports = class Room {
|
||||
this._hostOnlyRecording = false;
|
||||
// ##########################
|
||||
this.recording = {
|
||||
recSyncServerRecording: config?.server?.recording?.enabled || false,
|
||||
recSyncServerEndpoint: config?.server?.recording?.endpoint || '',
|
||||
recSyncServerRecording: config?.media?.recording?.enabled || false,
|
||||
recSyncServerEndpoint: config?.media?.recording?.endpoint || '',
|
||||
};
|
||||
// ##########################
|
||||
this._moderator = {
|
||||
@@ -49,9 +49,9 @@ module.exports = class Room {
|
||||
chat_cant_chatgpt: false,
|
||||
media_cant_sharing: false,
|
||||
};
|
||||
this.survey = config.survey;
|
||||
this.redirect = config.redirect;
|
||||
this.videoAIEnabled = config?.videoAI?.enabled || false;
|
||||
this.survey = config?.features?.survey;
|
||||
this.redirect = config?.features?.redirect;
|
||||
this.videoAIEnabled = config?.integrations?.videoAI?.enabled || false;
|
||||
this.peers = new Map();
|
||||
this.bannedPeers = [];
|
||||
this.webRtcTransport = config.mediasoup.webRtcTransport;
|
||||
@@ -62,12 +62,12 @@ module.exports = class Room {
|
||||
// RTMP configuration
|
||||
this.rtmpFileStreamer = null;
|
||||
this.rtmpUrlStreamer = null;
|
||||
this.rtmp = config.server.rtmp || false;
|
||||
this.rtmp = config?.media?.rtmp || false;
|
||||
|
||||
// Polls
|
||||
this.polls = [];
|
||||
|
||||
this.isHostProtected = config.host.protected;
|
||||
this.isHostProtected = config?.security?.host?.protected || false;
|
||||
|
||||
// Share Media
|
||||
this.shareMediaData = {};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
const ffmpegPath =
|
||||
config.server.rtmp && config.server.rtmp.ffmpegPath ? config.server.rtmp.ffmpegPath : '/usr/bin/ffmpeg';
|
||||
const ffmpegPath = config.media?.rtmp?.ffmpegPath || '/usr/bin/ffmpeg';
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
const config = require('./config');
|
||||
const { PassThrough } = require('stream');
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const ffmpegPath =
|
||||
config.server.rtmp && config.server.rtmp.ffmpegPath ? config.server.rtmp.ffmpegPath : '/usr/bin/ffmpeg';
|
||||
const ffmpegPath = config.media?.rtmp?.ffmpegPath || '/usr/bin/ffmpeg';
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
const Logger = require('./Logger');
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
const ffmpegPath =
|
||||
config.server.rtmp && config.server.rtmp.ffmpegPath ? config.server.rtmp.ffmpegPath : '/usr/bin/ffmpeg';
|
||||
const ffmpegPath = config.media?.rtmp?.ffmpegPath || '/usr/bin/ffmpeg';
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ dev dependencies: {
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.7.99
|
||||
* @version 1.8.00
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -107,8 +107,8 @@ const packageJson = require('../../package.json');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const crypto = require('crypto-js');
|
||||
const RtmpStreamer = require('./RtmpStreamer.js'); // Import the RtmpStreamer class
|
||||
const rtmpCfg = config.server.rtmp;
|
||||
const rtmpDir = rtmpCfg && rtmpCfg.dir ? rtmpCfg.dir : 'rtmp';
|
||||
const rtmpCfg = config?.media?.rtmp;
|
||||
const rtmpDir = rtmpCfg?.dir || 'rtmp';
|
||||
|
||||
// File and Url Rtmp streams count
|
||||
let rtmpFileStreamsCount = 0;
|
||||
@@ -120,14 +120,14 @@ const nodemailer = require('./lib/nodemailer');
|
||||
// Slack API
|
||||
const CryptoJS = require('crypto-js');
|
||||
const qS = require('qs');
|
||||
const slackEnabled = config.slack.enabled;
|
||||
const slackSigningSecret = config.slack.signingSecret;
|
||||
const slackEnabled = config?.integrations?.slack?.enabled || false;
|
||||
const slackSigningSecret = config?.integrations?.slack?.signingSecret || '';
|
||||
|
||||
const app = express();
|
||||
|
||||
const options = {
|
||||
cert: fs.readFileSync(path.join(__dirname, config.server.ssl.cert), 'utf-8'),
|
||||
key: fs.readFileSync(path.join(__dirname, config.server.ssl.key), 'utf-8'),
|
||||
cert: fs.readFileSync(path.join(__dirname, config?.server?.ssl.cert || '../ssl/cert.pem'), 'utf-8'),
|
||||
key: fs.readFileSync(path.join(__dirname, config?.server?.ssl.key || '../ssl/key.pem'), 'utf-8'),
|
||||
};
|
||||
|
||||
const corsOptions = {
|
||||
@@ -143,38 +143,38 @@ const io = socketIo(server, {
|
||||
cors: corsOptions,
|
||||
});
|
||||
|
||||
const host = config.server.hostUrl || `http://localhost:${config.server.listen.port}`;
|
||||
const trustProxy = !!config.server.trustProxy;
|
||||
const host = config?.server?.hostUrl || `http://localhost:${config?.server?.listen?.port || 3010}`;
|
||||
const trustProxy = Boolean(config?.server?.trustProxy);
|
||||
|
||||
const jwtCfg = {
|
||||
JWT_KEY: (config.jwt && config.jwt.key) || 'mirotalksfu_jwt_secret',
|
||||
JWT_EXP: (config.jwt && config.jwt.exp) || '1h',
|
||||
JWT_KEY: config?.security?.jwt?.key || 'mirotalksfu_jwt_secret',
|
||||
JWT_EXP: config?.security?.jwt?.exp || '1h',
|
||||
};
|
||||
|
||||
const hostCfg = {
|
||||
protected: config.host.protected,
|
||||
user_auth: config.host.user_auth,
|
||||
users: config.host.users,
|
||||
users_from_db: config.host.users_from_db,
|
||||
users_api_room_allowed: config.host.users_api_room_allowed,
|
||||
users_api_rooms_allowed: config.host.users_api_rooms_allowed,
|
||||
users_api_endpoint: config.host.users_api_endpoint,
|
||||
users_api_secret_key: config.host.users_api_secret_key,
|
||||
api_room_exists: config.host.api_room_exists,
|
||||
users: config.host.users,
|
||||
authenticated: !config.host.protected,
|
||||
protected: config?.security?.host?.protected,
|
||||
authenticated: !config?.security?.host?.protected,
|
||||
user_auth: config?.security?.host?.user_auth,
|
||||
users: config?.security?.host?.users,
|
||||
users_from_db: config?.security?.host?.users_from_db,
|
||||
users_api_room_allowed: config?.security?.host?.users_api_room_allowed,
|
||||
users_api_rooms_allowed: config?.security?.host?.users_api_rooms_allowed,
|
||||
users_api_endpoint: config?.security?.host?.users_api_endpoint,
|
||||
users_api_secret_key: config?.security?.host?.users_api_secret_key,
|
||||
api_room_exists: config?.security?.host?.api_room_exists,
|
||||
presenters: config?.security?.host?.presenters,
|
||||
};
|
||||
|
||||
const restApi = {
|
||||
basePath: '/api/v1', // api endpoint path
|
||||
docs: host + '/api/v1/docs', // api docs
|
||||
allowed: config.api?.allowed,
|
||||
allowed: config.api?.allowed || {},
|
||||
};
|
||||
|
||||
// Sentry monitoring
|
||||
const sentryEnabled = config.sentry.enabled;
|
||||
const sentryDSN = config.sentry.DSN;
|
||||
const sentryTracesSampleRate = config.sentry.tracesSampleRate;
|
||||
const sentryEnabled = config.integrations?.sentry?.enabled || false;
|
||||
const sentryDSN = config.integrations.sentry.DSN;
|
||||
const sentryTracesSampleRate = config.integrations.sentry.tracesSampleRate;
|
||||
if (sentryEnabled) {
|
||||
Sentry.init({
|
||||
dsn: sentryDSN,
|
||||
@@ -202,7 +202,7 @@ const webhook = {
|
||||
};
|
||||
|
||||
// Discord Bot
|
||||
const { enabled, commands, token } = config.discord || {};
|
||||
const { enabled, commands, token } = config?.integrations?.discord || {};
|
||||
|
||||
if (enabled && commands.length > 0 && token) {
|
||||
const discordBot = new Discord(token, commands);
|
||||
@@ -218,12 +218,12 @@ const defaultStats = {
|
||||
|
||||
// OpenAI/ChatGPT
|
||||
let chatGPT;
|
||||
if (config.chatGPT.enabled) {
|
||||
if (config.chatGPT.apiKey) {
|
||||
if (config?.integrations?.chatGPT?.enabled) {
|
||||
if (config?.integrations?.chatGPT?.apiKey) {
|
||||
const { OpenAI } = require('openai');
|
||||
const configuration = {
|
||||
basePath: config.chatGPT.basePath,
|
||||
apiKey: config.chatGPT.apiKey,
|
||||
basePath: config?.integrations?.chatGPT?.basePath,
|
||||
apiKey: config?.integrations?.chatGPT?.apiKey,
|
||||
};
|
||||
chatGPT = new OpenAI(configuration);
|
||||
} else {
|
||||
@@ -232,18 +232,18 @@ if (config.chatGPT.enabled) {
|
||||
}
|
||||
|
||||
// OpenID Connect
|
||||
const OIDC = config.oidc ? config.oidc : { enabled: false };
|
||||
const OIDC = config?.security?.oidc || { enabled: false };
|
||||
|
||||
// directory
|
||||
const dir = {
|
||||
public: path.join(__dirname, '../../', 'public'),
|
||||
rec: path.join(__dirname, '../', config?.server?.recording?.dir ? config.server.recording.dir + '/' : 'rec/'),
|
||||
rtmp: path.join(__dirname, '../', config?.rtmp?.dir ? config.rtmp.dir + '/' : 'rtmp/'),
|
||||
public: path.join(__dirname, '../../public'),
|
||||
rec: path.join(__dirname, '../', config?.media?.recording?.dir || 'rec', '/'),
|
||||
rtmp: path.join(__dirname, '../', config?.media?.rtmp?.dir || 'rtmp', '/'),
|
||||
};
|
||||
|
||||
// Rec directory create and set max file size
|
||||
const recMaxFileSize = config?.server?.recording?.maxFileSize || 1 * 1024 * 1024 * 1024; // 1GB default
|
||||
const serverRecordingEnabled = config?.server?.recording?.enabled;
|
||||
const recMaxFileSize = config?.media?.recording?.maxFileSize || 1 * 1024 * 1024 * 1024; // 1GB default
|
||||
const serverRecordingEnabled = config?.media?.recording?.enabled || false;
|
||||
if (serverRecordingEnabled) {
|
||||
log.debug('Server Recording enabled creating dir', dir.rtmp);
|
||||
if (!fs.existsSync(dir.rec)) {
|
||||
@@ -306,7 +306,7 @@ let nextMediasoupWorkerIdx = 0;
|
||||
// Autodetect announcedAddress with multiple fallback services
|
||||
if (!announcedAddress && IP === '0.0.0.0') {
|
||||
const detectPublicIp = async () => {
|
||||
const services = config.services?.ip || [
|
||||
const services = config.system?.services?.ip || [
|
||||
'http://api.ipify.org',
|
||||
'http://ipinfo.io/ip',
|
||||
'http://ifconfig.me/ip',
|
||||
@@ -554,12 +554,12 @@ function startServer() {
|
||||
|
||||
// UI buttons configuration
|
||||
app.get('/config', (req, res) => {
|
||||
res.status(200).json({ message: config.ui ? config.ui.buttons : false });
|
||||
res.status(200).json({ message: config?.ui?.buttons || false });
|
||||
});
|
||||
|
||||
// Brand configuration
|
||||
app.get('/brand', (req, res) => {
|
||||
res.status(200).json({ message: config.ui ? config.ui.brand : false });
|
||||
res.status(200).json({ message: config?.ui?.brand || false });
|
||||
});
|
||||
|
||||
// main page
|
||||
@@ -780,7 +780,7 @@ function startServer() {
|
||||
|
||||
// Get stats endpoint
|
||||
app.get('/stats', (req, res) => {
|
||||
const stats = config.stats ? config.stats : defaultStats;
|
||||
const stats = config?.features?.stats || defaultStats;
|
||||
// log.debug('Send stats', stats);
|
||||
res.send(stats);
|
||||
});
|
||||
@@ -833,12 +833,9 @@ function startServer() {
|
||||
authorizedIps: authHost.getAuthorizedIPs(),
|
||||
});
|
||||
|
||||
const isPresenter =
|
||||
config.presenters && config.presenters.join_first
|
||||
? true
|
||||
: config.presenters &&
|
||||
config.presenters.list &&
|
||||
config.presenters.list.includes(username).toString();
|
||||
const isPresenter = Boolean(
|
||||
hostCfg?.presenters?.join_first || hostCfg?.presenters?.list?.includes(username),
|
||||
);
|
||||
|
||||
const token = encodeToken({ username: username, password: password, presenter: isPresenter });
|
||||
const allowedRooms = await getUserAllowedRooms(username, password);
|
||||
@@ -848,8 +845,7 @@ function startServer() {
|
||||
|
||||
if (isPeerValid) {
|
||||
log.debug('PEER LOGIN OK', { ip: ip, authorized: true });
|
||||
const isPresenter =
|
||||
config.presenters && config.presenters.list && config.presenters.list.includes(username).toString();
|
||||
const isPresenter = hostCfg?.presenters?.list?.includes(username) || false;
|
||||
const token = encodeToken({ username: username, password: password, presenter: isPresenter });
|
||||
const allowedRooms = await getUserAllowedRooms(username, password);
|
||||
return res.status(200).json({ message: token, allowedRooms: allowedRooms });
|
||||
@@ -982,7 +978,9 @@ function startServer() {
|
||||
return res.status(400).send('RTMP server is not enabled or missing the config');
|
||||
}
|
||||
|
||||
const domainName = config.ngrok.enabled ? 'localhost' : req.headers.host.split(':')[0];
|
||||
const domainName = config?.integrations?.ngrok?.enabled
|
||||
? 'localhost'
|
||||
: req.headers.host?.split(':')[0] || 'localhost';
|
||||
|
||||
const rtmpServer = rtmpCfg.server != '' ? rtmpCfg.server : false;
|
||||
const rtmpServerAppName = rtmpCfg.appName != '' ? rtmpCfg.appName : 'live';
|
||||
@@ -1283,7 +1281,7 @@ function startServer() {
|
||||
// ####################################################
|
||||
|
||||
function getServerConfig(tunnel = false) {
|
||||
return {
|
||||
const safeConfig = {
|
||||
// Network & Connectivity
|
||||
network: {
|
||||
server_listen: host,
|
||||
@@ -1301,68 +1299,74 @@ function startServer() {
|
||||
|
||||
// Security & Authentication
|
||||
security: {
|
||||
cors_options: corsOptions,
|
||||
cors: corsOptions,
|
||||
jwtCfg: jwtCfg,
|
||||
hostProtected: hostCfg.protected || hostCfg.user_auth ? hostCfg : false,
|
||||
ip_lookup_enabled: config.IPLookup?.enabled ? config.IPLookup : false,
|
||||
oidc: OIDC.enabled ? OIDC : false,
|
||||
host: hostCfg?.protected || hostCfg?.user_auth ? hostCfg : { presenters: hostCfg.presenters },
|
||||
ip_lookup: config.integrations?.IPLookup?.enabled ? config.integrations.IPLookup : false,
|
||||
oidc: OIDC?.enabled ? OIDC : false,
|
||||
middleware: {
|
||||
IpWhitelist: config?.security?.middleware?.IpWhitelist?.enabled
|
||||
? config.security.middleware.IpWhitelist
|
||||
: false,
|
||||
//...
|
||||
},
|
||||
},
|
||||
|
||||
// API & Services
|
||||
api: {
|
||||
rest_api: restApi,
|
||||
webhook: webhook,
|
||||
presenters: config.presenters,
|
||||
webhook: webhook.enabled ? webhook : false,
|
||||
},
|
||||
|
||||
// Media Configuration
|
||||
media: {
|
||||
mediasoup: {
|
||||
listenInfos: config.mediasoup.webRtcTransport.listenInfos,
|
||||
worker_bin: mediasoup.workerBin,
|
||||
server_version: mediasoup.version,
|
||||
client_version: mediasoupClient.version,
|
||||
listenInfos: config.mediasoup?.webRtcTransport?.listenInfos,
|
||||
worker_bin: mediasoup?.workerBin,
|
||||
server_version: mediasoup?.version,
|
||||
client_version: mediasoupClient?.version,
|
||||
},
|
||||
rtmp_enabled: rtmpCfg.enabled ? rtmpCfg : false,
|
||||
videoAI_enabled: config.videoAI.enabled ? config.videoAI : false,
|
||||
server_recording: config?.server?.recording,
|
||||
rtmp_enabled: rtmpCfg?.enabled ? rtmpCfg : false,
|
||||
videoAI_enabled: config.integrations?.videoAI?.enabled ? config.integrations.videoAI : false,
|
||||
server_recording: config?.media?.recording?.enabled ? config.media.recording : false,
|
||||
},
|
||||
|
||||
// Communication Integrations
|
||||
integrations: {
|
||||
discord: config.discord?.enabled ? config.discord : false,
|
||||
mattermost: config.mattermost?.enabled ? config.mattermost : false,
|
||||
slack: slackEnabled ? config.slack : false,
|
||||
chatGPT: config.chatGPT?.enabled ? config.chatGPT : false,
|
||||
email_alerts: config.email?.alert ? config.email : false,
|
||||
discord: config.integrations?.discord?.enabled ? config.integrations.discord : false,
|
||||
mattermost: config.integrations?.mattermost?.enabled ? config.integrations.mattermost : false,
|
||||
slack: slackEnabled ? config.integrations?.slack : false,
|
||||
chatGPT: config.integrations?.chatGPT?.enabled ? config.integrations.chatGPT : false,
|
||||
email_alerts: config?.integrations?.email?.alert ? config.integrations.email : false,
|
||||
},
|
||||
|
||||
// UI & Branding
|
||||
ui: {
|
||||
brand: config.ui.brand,
|
||||
buttons: config.ui.buttons,
|
||||
middleware: config.middleware,
|
||||
brand: config.ui?.brand,
|
||||
buttons: config.ui?.buttons,
|
||||
},
|
||||
|
||||
// Monitoring & Analytics
|
||||
monitoring: {
|
||||
sentry: sentryEnabled ? config.sentry : false,
|
||||
stats: config.stats?.enabled ? config.stats : false,
|
||||
system_info: config.systemInfo,
|
||||
sentry: sentryEnabled ? config.integrations?.sentry : false,
|
||||
stats: config.features?.stats?.enabled ? config.features.stats : false,
|
||||
system_info: config.system?.info,
|
||||
},
|
||||
|
||||
// Features & Functionality
|
||||
features: {
|
||||
survey: config.survey?.enabled ? config.survey : false,
|
||||
redirect: config.redirect?.enabled ? config.redirect : false,
|
||||
survey: config.features?.survey?.enabled ? config.features.survey : false,
|
||||
redirect: config.features?.redirect?.enabled ? config.features.redirect : false,
|
||||
},
|
||||
|
||||
// Version Information
|
||||
versions: {
|
||||
app: packageJson.version,
|
||||
app: packageJson?.version,
|
||||
node: process.versions.node,
|
||||
},
|
||||
};
|
||||
|
||||
return safeConfig;
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
@@ -1371,8 +1375,8 @@ function startServer() {
|
||||
|
||||
async function ngrokStart() {
|
||||
try {
|
||||
await ngrok.authtoken(config.ngrok.authToken);
|
||||
const listener = await ngrok.forward({ addr: config.server.listen.port });
|
||||
await ngrok.authtoken(config?.integrations?.ngrok?.authToken);
|
||||
const listener = await ngrok.forward({ addr: config?.server?.listen?.port });
|
||||
const tunnelUrl = listener.url();
|
||||
log.info('Server config', getServerConfig(tunnelUrl));
|
||||
} catch (err) {
|
||||
@@ -1386,7 +1390,7 @@ function startServer() {
|
||||
// START SERVER
|
||||
// ####################################################
|
||||
|
||||
server.listen(config.server.listen.port, () => {
|
||||
server.listen(config?.server?.listen?.port || 3010, () => {
|
||||
log.log(
|
||||
`%c
|
||||
|
||||
@@ -1401,7 +1405,7 @@ function startServer() {
|
||||
'font-family:monospace',
|
||||
);
|
||||
|
||||
if (config.ngrok.enabled && config.ngrok.authToken !== '') {
|
||||
if (config?.integrations?.ngrok?.enabled && config?.integrations?.ngrok?.authToken !== '') {
|
||||
return ngrokStart();
|
||||
}
|
||||
log.info('Server config', getServerConfig());
|
||||
@@ -1527,7 +1531,7 @@ function startServer() {
|
||||
const peer_ip = getIpSocket(socket);
|
||||
|
||||
// Get peer Geo Location
|
||||
if (config.IPLookup.enabled && peer_ip != '::1') {
|
||||
if (config?.integrations?.IPLookup?.enabled && peer_ip != '::1') {
|
||||
dataObject.peer_geo = await getPeerGeoLocation(peer_ip);
|
||||
}
|
||||
|
||||
@@ -1569,10 +1573,10 @@ function startServer() {
|
||||
return cb('unauthorized');
|
||||
}
|
||||
|
||||
is_presenter =
|
||||
const is_presenter =
|
||||
presenter === '1' ||
|
||||
presenter === 'true' ||
|
||||
(config.presenters.join_first && room.getPeersCount() === 0);
|
||||
(hostCfg?.presenters?.join_first && room?.getPeersCount() === 0);
|
||||
|
||||
log.debug('[Join] - HOST PROTECTED - USER AUTH check peer', {
|
||||
ip: peer_ip,
|
||||
@@ -1637,7 +1641,7 @@ function startServer() {
|
||||
is_presenter: is_presenter,
|
||||
};
|
||||
// first we check if the username match the presenters username
|
||||
if (config.presenters && config.presenters.list && config.presenters.list.includes(peer_name)) {
|
||||
if (hostCfg?.presenters?.list?.includes(peer_name)) {
|
||||
presenters[socket.room_id][socket.id] = presenter;
|
||||
} else {
|
||||
// if not match the presenters username, the first one join room is the presenter
|
||||
@@ -2486,7 +2490,7 @@ function startServer() {
|
||||
socket.on('getChatGPT', async ({ time, room, name, prompt, context }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.chatGPT.enabled) return cb({ message: 'ChatGPT seems disabled, try later!' });
|
||||
if (!config?.integrations?.chatGPT?.enabled) return cb({ message: 'ChatGPT seems disabled, try later!' });
|
||||
|
||||
// https://platform.openai.com/docs/api-reference/completions/create
|
||||
try {
|
||||
@@ -2494,10 +2498,10 @@ function startServer() {
|
||||
context.push({ role: 'user', content: prompt });
|
||||
// Call OpenAI's API to generate response
|
||||
const completion = await chatGPT.chat.completions.create({
|
||||
model: config.chatGPT.model || 'gpt-3.5-turbo',
|
||||
model: config?.integrations?.chatGPT?.model || 'gpt-3.5-turbo',
|
||||
messages: context,
|
||||
max_tokens: config.chatGPT.max_tokens,
|
||||
temperature: config.chatGPT.temperature,
|
||||
max_tokens: config?.integrations?.chatGPT?.max_tokens,
|
||||
temperature: config?.integrations?.chatGPT?.temperature,
|
||||
});
|
||||
// Extract message from completion
|
||||
const message = completion.choices[0].message.content.trim();
|
||||
@@ -2532,14 +2536,14 @@ function startServer() {
|
||||
|
||||
// https://docs.heygen.com/reference/list-avatars-v2
|
||||
socket.on('getAvatarList', async ({}, cb) => {
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${config.videoAI.basePath}/v2/avatars`, {
|
||||
const response = await axios.get(`${config?.integrations?.videoAI?.basePath}/v2/avatars`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2556,14 +2560,14 @@ function startServer() {
|
||||
|
||||
// https://docs.heygen.com/reference/list-voices-v2
|
||||
socket.on('getVoiceList', async ({}, cb) => {
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${config.videoAI.basePath}/v2/voices`, {
|
||||
const response = await axios.get(`${config?.integrations?.videoAI?.basePath}/v2/voices`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2582,12 +2586,12 @@ function startServer() {
|
||||
socket.on('streamingNew', async ({ quality, avatar_id, voice_id }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
try {
|
||||
const voice = voice_id ? { voice_id: voice_id } : {};
|
||||
const response = await axios.post(
|
||||
`${config.videoAI.basePath}/v1/streaming.new`,
|
||||
`${config?.integrations?.videoAI?.basePath}/v1/streaming.new`,
|
||||
{
|
||||
quality,
|
||||
avatar_id,
|
||||
@@ -2597,7 +2601,7 @@ function startServer() {
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
'x-api-key': config.videoAI.apiKey,
|
||||
'x-api-key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -2617,17 +2621,17 @@ function startServer() {
|
||||
socket.on('streamingStart', async ({ session_id, sdp }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${config.videoAI.basePath}/v1/streaming.start`,
|
||||
`${config?.integrations?.videoAI?.basePath}/v1/streaming.start`,
|
||||
{ session_id, sdp },
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -2647,17 +2651,17 @@ function startServer() {
|
||||
socket.on('streamingICE', async ({ session_id, candidate }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${config.videoAI.basePath}/v1/streaming.ice`,
|
||||
`${config?.integrations?.videoAI?.basePath}/v1/streaming.ice`,
|
||||
{ session_id, candidate },
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -2677,12 +2681,12 @@ function startServer() {
|
||||
socket.on('streamingTask', async ({ session_id, text }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${config.videoAI.basePath}/v1/streaming.task`,
|
||||
`${config?.integrations?.videoAI?.basePath}/v1/streaming.task`,
|
||||
{
|
||||
session_id,
|
||||
text,
|
||||
@@ -2690,7 +2694,7 @@ function startServer() {
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -2710,19 +2714,19 @@ function startServer() {
|
||||
socket.on('streamingInterrupt', async ({ session_id, text }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${config.videoAI.basePath}/v1/streaming.interrupt`,
|
||||
`${config?.integrations?.videoAI?.basePath}/v1/streaming.interrupt`,
|
||||
{
|
||||
session_id,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -2741,10 +2745,11 @@ function startServer() {
|
||||
socket.on('talkToOpenAI', async ({ text, context }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const systemLimit = config.videoAI.systemLimit;
|
||||
const systemLimit = config?.integrations?.videoAI?.systemLimit;
|
||||
const arr = {
|
||||
messages: [...context, { role: 'system', content: systemLimit }, { role: 'user', content: text }],
|
||||
model: 'gpt-3.5-turbo',
|
||||
@@ -2769,18 +2774,19 @@ function startServer() {
|
||||
socket.on('streamingStop', async ({ session_id }, cb) => {
|
||||
if (!roomExists(socket)) return;
|
||||
|
||||
if (!config.videoAI.enabled || !config.videoAI.apiKey)
|
||||
if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey)
|
||||
return cb({ error: 'Video AI seems disabled, try later!' });
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${config.videoAI.basePath}/v1/streaming.stop`,
|
||||
`${config?.integrations?.videoAI?.basePath}/v1/streaming.stop`,
|
||||
{
|
||||
session_id,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': config.videoAI.apiKey,
|
||||
'X-Api-Key': config?.integrations?.videoAI?.apiKey,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -2820,7 +2826,12 @@ function startServer() {
|
||||
if (!isPresenter) return cb(false);
|
||||
|
||||
const room = getRoom(socket);
|
||||
const host = config.ngrok.enabled ? 'localhost' : socket.handshake.headers.host.split(':')[0];
|
||||
|
||||
const DEFAULT_HOST = 'localhost';
|
||||
const host = config?.ngrok?.enabled
|
||||
? DEFAULT_HOST
|
||||
: socket?.handshake?.headers?.host?.split(':')[0] || DEFAULT_HOST;
|
||||
|
||||
const rtmp = await room.startRTMP(socket.id, room, host, 1935, `../${rtmpDir}/${file}`);
|
||||
|
||||
if (rtmp !== false) rtmpFileStreamsCount++;
|
||||
@@ -2864,7 +2875,12 @@ function startServer() {
|
||||
if (!isPresenter) return cb(false);
|
||||
|
||||
const room = getRoom(socket);
|
||||
const host = config.ngrok.enabled ? 'localhost' : socket.handshake.headers.host.split(':')[0];
|
||||
|
||||
const DEFAULT_HOST = 'localhost';
|
||||
const host = config?.integrations?.ngrok?.enabled
|
||||
? DEFAULT_HOST
|
||||
: socket?.handshake?.headers?.host?.split(':')[0] || DEFAULT_HOST;
|
||||
|
||||
const rtmp = await room.startRTMPfromURL(socket.id, room, host, 1935, inputVideoURL);
|
||||
|
||||
if (rtmp !== false) rtmpUrlStreamsCount++;
|
||||
@@ -3266,11 +3282,7 @@ function startServer() {
|
||||
|
||||
function isPeerPresenter(room_id, peer_id, peer_name, peer_uuid) {
|
||||
try {
|
||||
if (
|
||||
config.presenters &&
|
||||
config.presenters.join_first &&
|
||||
(!presenters[room_id] || !presenters[room_id][peer_id])
|
||||
) {
|
||||
if (hostCfg?.presenters?.join_first && (!presenters[room_id] || !presenters[room_id][peer_id])) {
|
||||
// Presenter not in the presenters config list, disconnected, or peer_id changed...
|
||||
for (const [existingPeerID, presenter] of Object.entries(presenters[room_id] || {})) {
|
||||
if (presenter.peer_name === peer_name) {
|
||||
@@ -3286,13 +3298,13 @@ function startServer() {
|
||||
}
|
||||
|
||||
const isPresenter =
|
||||
(config.presenters &&
|
||||
config.presenters.join_first &&
|
||||
typeof presenters[room_id] === 'object' &&
|
||||
Object.keys(presenters[room_id][peer_id]).length > 1 &&
|
||||
presenters[room_id][peer_id]['peer_name'] === peer_name &&
|
||||
presenters[room_id][peer_id]['peer_uuid'] === peer_uuid) ||
|
||||
(config.presenters && config.presenters.list && config.presenters.list.includes(peer_name));
|
||||
// First condition: join_first validation
|
||||
(hostCfg?.presenters?.join_first &&
|
||||
presenters[room_id]?.[peer_id]?.peer_name === peer_name &&
|
||||
presenters[room_id]?.[peer_id]?.peer_uuid === peer_uuid &&
|
||||
Object.keys(presenters[room_id]?.[peer_id] || {}).length > 1) ||
|
||||
// Fallback condition: list check
|
||||
hostCfg?.presenters?.list?.includes(peer_name);
|
||||
|
||||
log.debug('isPeerPresenter', {
|
||||
room_id: room_id,
|
||||
@@ -3491,7 +3503,7 @@ function startServer() {
|
||||
|
||||
// Get allowed rooms for user from config.js file
|
||||
if (hostCfg.protected && !hostCfg.users_from_db) {
|
||||
const isOIDCEnabled = config.oidc && config.oidc.enabled;
|
||||
const isOIDCEnabled = config?.security?.oidc?.enabled;
|
||||
|
||||
const user = hostCfg.users.find((user) => user.displayname === username || user.username === username);
|
||||
|
||||
@@ -3512,7 +3524,7 @@ function startServer() {
|
||||
|
||||
if (!username || !room) return false;
|
||||
|
||||
const isOIDCEnabled = config.oidc && config.oidc.enabled;
|
||||
const isOIDCEnabled = config?.security?.oidc?.enabled;
|
||||
|
||||
if (hostCfg.protected || hostCfg.user_auth) {
|
||||
// Check if allowed room for user from DB...
|
||||
@@ -3539,7 +3551,7 @@ function startServer() {
|
||||
}
|
||||
}
|
||||
|
||||
const isInPresenterLists = config.presenters.list.includes(username);
|
||||
const isInPresenterLists = hostCfg?.presenters?.list?.includes(username);
|
||||
|
||||
if (isInPresenterLists) {
|
||||
log.debug('isRoomAllowedForUser - user in presenters list room allowed', room);
|
||||
@@ -3571,7 +3583,7 @@ function startServer() {
|
||||
}
|
||||
|
||||
async function getPeerGeoLocation(ip) {
|
||||
const endpoint = config.IPLookup.getEndpoint(ip);
|
||||
const endpoint = config?.integrations?.IPLookup?.getEndpoint(ip);
|
||||
log.debug('Get peer geo', { ip: ip, endpoint: endpoint });
|
||||
return axios
|
||||
.get(endpoint)
|
||||
|
||||
@@ -6,8 +6,8 @@ const CryptoJS = require('crypto-js');
|
||||
const config = require('./config');
|
||||
const { v4: uuidV4 } = require('uuid');
|
||||
|
||||
const JWT_KEY = (config.jwt && config.jwt.key) || 'mirotalksfu_jwt_secret';
|
||||
const JWT_EXP = (config.jwt && config.jwt.exp) || '1h';
|
||||
const JWT_KEY = config.security?.jwt?.key || 'mirotalksfu_jwt_secret';
|
||||
const JWT_EXP = config.security?.jwt?.exp || '1h';
|
||||
|
||||
module.exports = class ServerApi {
|
||||
constructor(host = null, authorization = null) {
|
||||
|
||||
تم حذف اختلاف الملف لأن الملف كبير جداً
تحميل الاختلاف
@@ -9,12 +9,13 @@ const log = new Logger('NodeMailer');
|
||||
// EMAIL CONFIG
|
||||
// ####################################################
|
||||
|
||||
const EMAIL_HOST = config.email ? config.email.host : false;
|
||||
const EMAIL_PORT = config.email ? config.email.port : false;
|
||||
const EMAIL_USERNAME = config.email ? config.email.username : false;
|
||||
const EMAIL_PASSWORD = config.email ? config.email.password : false;
|
||||
const EMAIL_SEND_TO = config.email ? config.email.sendTo : false;
|
||||
const EMAIL_ALERT = config.email ? config.email.alert : false;
|
||||
const emailConfig = config.integrations?.email || {};
|
||||
const EMAIL_ALERT = emailConfig.alert || false;
|
||||
const EMAIL_HOST = emailConfig.host || false;
|
||||
const EMAIL_PORT = emailConfig.port || false;
|
||||
const EMAIL_USERNAME = emailConfig.username || false;
|
||||
const EMAIL_PASSWORD = emailConfig.password || false;
|
||||
const EMAIL_SEND_TO = emailConfig.sendTo || false;
|
||||
|
||||
if (EMAIL_ALERT && EMAIL_HOST && EMAIL_PORT && EMAIL_USERNAME && EMAIL_PASSWORD && EMAIL_SEND_TO) {
|
||||
log.info('Email', {
|
||||
|
||||
@@ -4,11 +4,10 @@ const config = require('../config');
|
||||
const Logger = require('../Logger');
|
||||
const log = new Logger('RestrictAccessByIP');
|
||||
|
||||
const IpWhitelistEnabled = config.middleware ? config.middleware.IpWhitelist.enabled : false;
|
||||
const allowedIPs = config.middleware ? config.middleware.IpWhitelist.allowed : [];
|
||||
const { enabled = false, allowedIPs = [] } = config?.security?.middleware?.IpWhitelist || {};
|
||||
|
||||
const restrictAccessByIP = (req, res, next) => {
|
||||
if (!IpWhitelistEnabled) return next();
|
||||
if (!enabled) return next();
|
||||
//
|
||||
const clientIP =
|
||||
req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.socket.remoteAddress || req.ip;
|
||||
|
||||
@@ -96,6 +96,7 @@ if [ "$answer" != "${answer#[Yy]}" ] ;then
|
||||
fi
|
||||
|
||||
CONFIG=app/src/config.js
|
||||
ENV=.env
|
||||
|
||||
if ! [ -f "$CONFIG" ]; then
|
||||
|
||||
@@ -103,6 +104,7 @@ if ! [ -f "$CONFIG" ]; then
|
||||
|
||||
cp app/src/config.template.js $CONFIG
|
||||
|
||||
cp .env.template $ENV
|
||||
fi
|
||||
|
||||
printf 'Use docker (y/n)? '
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalksfu",
|
||||
"version": "1.7.99",
|
||||
"version": "1.8.00",
|
||||
"description": "WebRTC SFU browser-based video calls",
|
||||
"main": "Server.js",
|
||||
"scripts": {
|
||||
@@ -59,7 +59,7 @@
|
||||
"dependencies": {
|
||||
"@mattermost/client": "10.6.0",
|
||||
"@ngrok/ngrok": "1.4.1",
|
||||
"@sentry/node": "^9.10.0",
|
||||
"@sentry/node": "^9.10.1",
|
||||
"axios": "^1.8.4",
|
||||
"chokidar": "^4.0.3",
|
||||
"colors": "1.4.0",
|
||||
|
||||
@@ -64,7 +64,7 @@ let BRAND = {
|
||||
},
|
||||
about: {
|
||||
imageUrl: '../images/mirotalk-logo.gif',
|
||||
title: '<strong>WebRTC SFU v1.7.99</strong>',
|
||||
title: '<strong>WebRTC SFU v1.8.00</strong>',
|
||||
html: `
|
||||
<button
|
||||
id="support-button"
|
||||
|
||||
@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.7.99
|
||||
* @version 1.8.00
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -5294,7 +5294,7 @@ function showAbout() {
|
||||
position: 'center',
|
||||
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
|
||||
customClass: { image: 'img-about' },
|
||||
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.99',
|
||||
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.8.00',
|
||||
html: `
|
||||
<br />
|
||||
<div id="about">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.7.99
|
||||
* @version 1.8.00
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -9138,7 +9138,7 @@ class RoomClient {
|
||||
//console.log('VOICES LISTS', completion.response.voices);
|
||||
|
||||
// Ensure the response has the list of voices
|
||||
const voiceList = completion?.response?.voices ?? [];
|
||||
const voiceList = completion?.response?.voices || [];
|
||||
if (!voiceList.length) {
|
||||
console.warn('No voices available in the response');
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalk-webhook",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "MiroTalk WebHook endpoint example",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
@@ -14,6 +14,6 @@
|
||||
"license": "AGPLv3",
|
||||
"dependencies": {
|
||||
"express": "4.21.2",
|
||||
"body-parser": "1.20.3"
|
||||
"body-parser": "2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم