[mirotalksfu] - Improve config.js.template, add .env.template, update dep

هذا الالتزام موجود في:
Miroslav Pejic
2025-03-29 00:54:08 +01:00
الأصل 531b1342c5
التزام e3511513bc
19 ملفات معدلة مع 1594 إضافات و834 حذوفات

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)

عرض الملف

@@ -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"
}
}