Skip to content

Commit

Permalink
[mirotalksfu] - add HtmlInjector
Browse files Browse the repository at this point in the history
  • Loading branch information
miroslavpejic85 committed Jan 29, 2025
1 parent 9d56d81 commit 6e573b8
Show file tree
Hide file tree
Showing 16 changed files with 158 additions and 165 deletions.
95 changes: 95 additions & 0 deletions app/src/HtmlInjector.js
Original file line number Diff line number Diff line change
@@ -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;
65 changes: 35 additions & 30 deletions app/src/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ dev dependencies: {
* @license For commercial or closed source, contact us at [email protected] or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - [email protected]
* @version 1.7.17
* @version 1.7.18
*
*/

Expand All @@ -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');
Expand Down Expand Up @@ -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'),
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
});

Expand All @@ -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) {
Expand All @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -632,17 +637,17 @@ 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) {
const roomExists = hostCfg.users.some(
(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('/');
Expand All @@ -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('/');
Expand All @@ -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);

Expand Down Expand Up @@ -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) {
//
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions public/js/Room.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 [email protected] or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - [email protected]
* @version 1.7.17
* @version 1.7.18
*
*/

Expand Down Expand Up @@ -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: `
<br />
<div id="about">
Expand Down
2 changes: 1 addition & 1 deletion public/js/RoomClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @license For commercial or closed source, contact us at [email protected] or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - [email protected]
* @version 1.7.17
* @version 1.7.18
*
*/

Expand Down
13 changes: 0 additions & 13 deletions public/views/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>

<!-- https://ogp.me -->

<meta id="ogType" property="og:type" content="app-webrtc" />
<meta id="ogSiteName" property="og:site_name" content="MiroTalk SFU" />
<meta id="ogTitle" property="og:title" content="Click the link to make a call." />
<meta
id="ogDescription"
property="og:description"
content="MiroTalk SFU calling provides real-time video calls, messaging and screen sharing."
/>
<meta id="ogImage" property="og:image" content="https://sfu.mirotalk.com/images/mirotalksfu.png" />
<meta id="ogUrl" property="og:url" content="https://sfu.mirotalk.com" />

<!-- StyleSheet -->

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
Expand Down
Loading

0 comments on commit 6e573b8

Please sign in to comment.