diff --git a/app/src/HtmlInjector.js b/app/src/HtmlInjector.js new file mode 100644 index 00000000..56f5d426 --- /dev/null +++ b/app/src/HtmlInjector.js @@ -0,0 +1,95 @@ +const fs = require('fs'); + +const Logger = require('./Logger'); + +const log = new Logger('HtmlInjection'); + +class HtmlInjector { + constructor(filesPath, config) { + this.filesPath = filesPath; // Array of file paths to cache + this.cache = {}; // Object to store cached files + this.config = config; // Configuration containing metadata (OG, title, etc.) + this.injectData = this.getInjectData(); // Initialize dynamic injection data + this.preloadPages(filesPath); // Preload pages at startup + this.watchFiles(filesPath); // Watch files for changes + log.info('filesPath cached', this.filesPath); + } + + // Function to get dynamic data for injection (e.g., OG data, title, etc.) + getInjectData() { + return { + OG_TYPE: this.config.og?.type || 'app-webrtc', + OG_SITE_NAME: this.config.og?.siteName || 'MiroTalk SFU', + OG_TITLE: this.config.og?.title || 'Click the link to make a call.', + OG_DESCRIPTION: + this.config.og?.description || + 'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.', + OG_IMAGE: this.config.og?.image || 'https://sfu.mirotalk.com/images/mirotalksfu.png', + OG_URL: this.config.og?.url || 'https://sfu.mirotalk.com', + // Add more data here as needed with fallbacks + }; + } + + // Function to load a file into the cache + loadFileToCache(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf-8'); + this.cache[filePath] = content; // Store the content in cache + } catch (err) { + log.error(`Error reading file: ${filePath}`, err); + } + } + + // Function to preload pages into the cache + preloadPages(filePaths) { + filePaths.forEach((filePath) => this.loadFileToCache(filePath)); + } + + // Function to watch a file for changes and reload the cache + watchFileForChanges(filePath) { + fs.watch(filePath, (eventType) => { + if (eventType === 'change') { + log.debug(`File changed: ${filePath}`); + this.loadFileToCache(filePath); + log.debug(`Reload the file ${filePath} into cache`); + } + }); + } + + // Function to watch all files for changes + watchFiles(filePaths) { + filePaths.forEach((filePath) => this.watchFileForChanges(filePath)); + } + + // Function to inject dynamic data (e.g., OG, TITLE, etc.) into a given file + injectHtml(filePath, res) { + // return res.send(this.cache[filePath]); + + if (!this.cache[filePath]) { + log.error(`File not cached: ${filePath}`); + if (!res.headersSent) { + return res.status(500).send('Server Error'); + } + return; + } + + try { + // Replace placeholders with dynamic data (OG, TITLE, etc.) + const modifiedHTML = this.cache[filePath].replace( + /{{(OG_[A-Z_]+)}}/g, + (_, key) => this.injectData[key] || '', + ); + + if (!res.headersSent) { + res.send(modifiedHTML); + } + } catch (error) { + log.error('Error injecting HTML data:', error); + if (!res.headersSent) { + res.status(500).send('Server Error'); + } + } + } +} + +module.exports = HtmlInjector; diff --git a/app/src/Server.js b/app/src/Server.js index 0d6f60c5..1b63cc22 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -55,7 +55,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.17 + * @version 1.7.18 * */ @@ -81,6 +81,7 @@ const Peer = require('./Peer'); const ServerApi = require('./ServerApi'); const Logger = require('./Logger'); const Validator = require('./Validator'); +const HtmlInjector = require('./HtmlInjector'); const log = new Logger('Server'); const yaml = require('js-yaml'); const swaggerUi = require('swagger-ui-express'); @@ -237,6 +238,7 @@ if (serverRecordingEnabled) { // html views const views = { + html: path.join(__dirname, '../../public/views'), about: path.join(__dirname, '../../', 'public/views/about.html'), landing: path.join(__dirname, '../../', 'public/views/landing.html'), login: path.join(__dirname, '../../', 'public/views/login.html'), @@ -249,6 +251,10 @@ const views = { whoAreYou: path.join(__dirname, '../../', 'public/views/whoAreYou.html'), }; +const filesPath = [views.landing, views.newRoom, views.room, views.login]; + +const htmlInjector = new HtmlInjector(filesPath, config.ui.brand); + const authHost = new Host(); // Authenticated IP by Login const roomList = new Map(); // All Rooms @@ -457,20 +463,19 @@ function startServer() { }); // main page - app.get(['/'], OIDCAuth, (req, res) => { + app.get('/', OIDCAuth, (req, res) => { //log.debug('/ - hostCfg ----->', hostCfg); - if (!OIDC.enabled && hostCfg.protected) { const ip = getIP(req); if (allowedIP(ip)) { - res.sendFile(views.landing); + htmlInjector.injectHtml(views.landing, res); hostCfg.authenticated = true; } else { hostCfg.authenticated = false; res.redirect('/login'); } } else { - res.sendFile(views.landing); + return htmlInjector.injectHtml(views.landing, res); } }); @@ -483,7 +488,7 @@ function startServer() { }); // set new room name and join - app.get(['/newroom'], OIDCAuth, (req, res) => { + app.get('/newroom', OIDCAuth, (req, res) => { //log.info('/newroom - hostCfg ----->', hostCfg); if (!OIDC.enabled && hostCfg.protected) { @@ -496,12 +501,12 @@ function startServer() { res.redirect('/login'); } } else { - res.sendFile(views.newRoom); + htmlInjector.injectHtml(views.newRoom, res); } }); // Check if room active (exists) - app.post(['/isRoomActive'], (req, res) => { + app.post('/isRoomActive', (req, res) => { const { roomId } = checkXSS(req.body); if (roomId && (hostCfg.protected || hostCfg.user_auth)) { @@ -571,8 +576,8 @@ function startServer() { } catch (err) { log.error('Direct Join JWT error', { error: err.message, token: token }); return hostCfg.protected || hostCfg.user_auth - ? res.sendFile(views.login) - : res.sendFile(views.landing); + ? htmlInjector.injectHtml(views.login, res) + : htmlInjector.injectHtml(views.landing, res); } } else { const allowRoomAccess = isAllowedRoomAccess('/join/params', req, hostCfg, roomList, room); @@ -601,9 +606,9 @@ function startServer() { } if (room && (hostCfg.authenticated || isPeerValid)) { - return res.sendFile(views.room); + return htmlInjector.injectHtml(views.room, res); } else { - return res.sendFile(views.login); + return htmlInjector.injectHtml(views.login, res); } } @@ -632,7 +637,7 @@ function startServer() { if (!OIDC.enabled && hostCfg.protected && hostCfg.users_from_db) { const roomExists = await roomExistsForUser(roomId); log.debug('/join/:roomId exists from API endpoint', roomExists); - return roomExists ? res.sendFile(views.room) : res.redirect('/login'); + return roomExists ? htmlInjector.injectHtml(views.room, res) : res.redirect('/login'); } // 2. Protect room access with configuration check if (!OIDC.enabled && hostCfg.protected && !hostCfg.users_from_db) { @@ -640,9 +645,9 @@ function startServer() { (user) => user.allowed_rooms && (user.allowed_rooms.includes(roomId) || roomList.has(roomId)), ); log.debug('/join/:roomId exists from config allowed rooms', roomExists); - return roomExists ? res.sendFile(views.room) : res.redirect('/whoAreYou/' + roomId); + return roomExists ? htmlInjector.injectHtml(views.room, res) : res.redirect('/whoAreYou/' + roomId); } - res.sendFile(views.room); + htmlInjector.injectHtml(views.room, res); } else { // Who are you? !OIDC.enabled && hostCfg.protected ? res.redirect('/whoAreYou/' + roomId) : res.redirect('/'); @@ -655,42 +660,42 @@ function startServer() { }); // if not allow video/audio - app.get(['/permission'], (req, res) => { + app.get('/permission', (req, res) => { res.sendFile(views.permission); }); // privacy policy - app.get(['/privacy'], (req, res) => { + app.get('/privacy', (req, res) => { res.sendFile(views.privacy); }); // mirotalk about - app.get(['/about'], (req, res) => { + app.get('/about', (req, res) => { res.sendFile(views.about); }); // Get stats endpoint - app.get(['/stats'], (req, res) => { + app.get('/stats', (req, res) => { const stats = config.stats ? config.stats : defaultStats; // log.debug('Send stats', stats); res.send(stats); }); // handle who are you: Presenter or Guest - app.get(['/whoAreYou/:roomId'], (req, res) => { + app.get('/whoAreYou/:roomId', (req, res) => { res.sendFile(views.whoAreYou); }); // handle login if user_auth enabled - app.get(['/login'], (req, res) => { + app.get('/login', (req, res) => { if (hostCfg.protected || hostCfg.user_auth) { - return res.sendFile(views.login); + return htmlInjector.injectHtml(views.login, res); } res.redirect('/'); }); // handle logged on host protected - app.get(['/logged'], (req, res) => { + app.get('/logged', (req, res) => { const ip = getIP(req); if (allowedIP(ip)) { res.redirect('/'); @@ -706,7 +711,7 @@ function startServer() { // #################################################### // handle login on host protected - app.post(['/login'], async (req, res) => { + app.post('/login', async (req, res) => { const ip = getIP(req); log.debug(`Request login to host from: ${ip}`, req.body); @@ -753,7 +758,7 @@ function startServer() { // KEEP RECORDING ON SERVER DIR // #################################################### - app.post(['/recSync'], (req, res) => { + app.post('/recSync', (req, res) => { // Store recording... if (serverRecordingEnabled) { // @@ -940,7 +945,7 @@ function startServer() { // REST API // #################################################### - app.get([restApi.basePath + '/stats'], (req, res) => { + app.get(restApi.basePath + '/stats', (req, res) => { try { // Check if endpoint allowed if (restApi.allowed && !restApi.allowed.stats) { @@ -985,7 +990,7 @@ function startServer() { }); // request meetings list - app.get([restApi.basePath + '/meetings'], (req, res) => { + app.get(restApi.basePath + '/meetings', (req, res) => { // Check if endpoint allowed if (restApi.allowed && !restApi.allowed.meetings) { return res.status(403).json({ @@ -1014,7 +1019,7 @@ function startServer() { }); // request meeting room endpoint - app.post([restApi.basePath + '/meeting'], (req, res) => { + app.post(restApi.basePath + '/meeting', (req, res) => { // Check if endpoint allowed if (restApi.allowed && !restApi.allowed.meeting) { return res.status(403).json({ @@ -1043,7 +1048,7 @@ function startServer() { }); // request join room endpoint - app.post([restApi.basePath + '/join'], (req, res) => { + app.post(restApi.basePath + '/join', (req, res) => { // Check if endpoint allowed if (restApi.allowed && !restApi.allowed.join) { return res.status(403).json({ @@ -1072,7 +1077,7 @@ function startServer() { }); // request token endpoint - app.post([restApi.basePath + '/token'], (req, res) => { + app.post(restApi.basePath + '/token', (req, res) => { // Check if endpoint allowed if (restApi.allowed && !restApi.allowed.token) { return res.status(403).json({ diff --git a/package.json b/package.json index 1c94ee6e..79098b1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.7.17", + "version": "1.7.18", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { diff --git a/public/js/Room.js b/public/js/Room.js index fad8150a..6593d429 100644 --- a/public/js/Room.js +++ b/public/js/Room.js @@ -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.17 + * @version 1.7.18 * */ @@ -4905,7 +4905,7 @@ function showAbout() { imageUrl: image.about, customClass: { image: 'img-about' }, position: 'center', - title: 'WebRTC SFU v1.7.17', + title: 'WebRTC SFU v1.7.18', html: `
diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index e0ec6bac..c3641791 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -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.17 + * @version 1.7.18 * */ diff --git a/public/views/404.html b/public/views/404.html index 44916be2..066d5e27 100755 --- a/public/views/404.html +++ b/public/views/404.html @@ -24,19 +24,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - - diff --git a/public/views/50X.html b/public/views/50X.html index db34732f..853fcaa6 100755 --- a/public/views/50X.html +++ b/public/views/50X.html @@ -24,19 +24,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - - diff --git a/public/views/Room.html b/public/views/Room.html index 4a81e6f4..16d5303a 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -28,16 +28,12 @@ - - - - - - + + + + + + diff --git a/public/views/RtmpStreamer.html b/public/views/RtmpStreamer.html index 776bde16..87dcf2a4 100644 --- a/public/views/RtmpStreamer.html +++ b/public/views/RtmpStreamer.html @@ -27,19 +27,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - - diff --git a/public/views/about.html b/public/views/about.html index b7da17b8..08ecaa39 100644 --- a/public/views/about.html +++ b/public/views/about.html @@ -24,19 +24,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - - diff --git a/public/views/landing.html b/public/views/landing.html index d76d9ee8..0e0173ab 100644 --- a/public/views/landing.html +++ b/public/views/landing.html @@ -26,16 +26,12 @@ - - - - - - + + + + + + diff --git a/public/views/login.html b/public/views/login.html index 557f4c61..8281f949 100644 --- a/public/views/login.html +++ b/public/views/login.html @@ -26,16 +26,12 @@ - - - - - - + + + + + + diff --git a/public/views/newroom.html b/public/views/newroom.html index b08142a5..a3e1c82c 100755 --- a/public/views/newroom.html +++ b/public/views/newroom.html @@ -26,16 +26,12 @@ - - - - - - + + + + + + diff --git a/public/views/permission.html b/public/views/permission.html index 2dc5110d..c31f3cb1 100755 --- a/public/views/permission.html +++ b/public/views/permission.html @@ -24,19 +24,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - - diff --git a/public/views/privacy.html b/public/views/privacy.html index 1965925d..8ce5a386 100755 --- a/public/views/privacy.html +++ b/public/views/privacy.html @@ -24,19 +24,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - - diff --git a/public/views/whoAreYou.html b/public/views/whoAreYou.html index 80aa4612..f0c0c581 100755 --- a/public/views/whoAreYou.html +++ b/public/views/whoAreYou.html @@ -24,19 +24,6 @@ content="webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting" /> - - - - - - - - -