[mirotalk] - add HtmlInjector, and OG customizations

This commit is contained in:
Miroslav Pejic
2025-01-29 16:47:37 +01:00
parent daa94f0f9a
commit 7aebb9bae7
12 changed files with 160 additions and 93 deletions
+95
View File
@@ -0,0 +1,95 @@
const fs = require('fs');
const Logger = require('./logs');
const log = new Logger('HtmlInjector');
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',
OG_TITLE: this.config.og?.title || 'Click the link to make a call.',
OG_DESCRIPTION:
this.config.og?.description ||
'MiroTalk calling provides real-time HD quality and latency simply not available with traditional technology.',
OG_IMAGE: this.config.og?.image || 'https://p2p.mirotalk.com/images/preview.png',
OG_URL: this.config.og?.url || 'https://p2p.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;
+9
View File
@@ -13,6 +13,15 @@ module.exports = {
joinButtonLabel: 'JOIN ROOM',
joinLastLabel: 'Your recent room:',
},
og: {
type: 'app-webrtc',
siteName: 'MiroTalk',
title: 'Click the link to make a call.',
description:
'MiroTalk calling provides real-time HD quality and latency simply not available with traditional technology.',
image: 'https://p2p.mirotalk.com/images/preview.png',
url: 'https://p2p.mirotalk.com',
},
site: {
shortcutIcon: '../images/logo.svg',
appleTouchIcon: '../images/logo.svg',
+27 -20
View File
@@ -39,7 +39,7 @@ dependencies: {
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.4.69
* @version 1.4.70
*
*/
@@ -61,8 +61,9 @@ const app = express();
const fs = require('fs');
const checkXSS = require('./xss.js');
const ServerApi = require('./api');
const mattermostCli = require('./mattermost.js');
const mattermostCli = require('./mattermost');
const Validate = require('./validate');
const HtmlInjector = require('./HtmlInjector.js');
const Host = require('./host');
const Logs = require('./logs');
const log = new Logs('server');
@@ -369,6 +370,10 @@ const views = {
stunTurn: path.join(__dirname, '../../', 'public/views/testStunTurn.html'),
};
// File to cache and inject custom HTML data like OG tags and any other elements.
const filesPath = [views.landing, views.newCall, views.client, views.login];
const htmlInjector = new HtmlInjector(filesPath, config.brand);
const channels = {}; // collect channels
const sockets = {}; // collect sockets
const peers = {}; // collect peers info grp by channels
@@ -482,23 +487,23 @@ app.get('/logout', (req, res) => {
});
// main page
app.get(['/'], OIDCAuth, (req, res) => {
app.get('/', OIDCAuth, (req, res) => {
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);
}
});
// set new room name and join
app.get(['/newcall'], OIDCAuth, (req, res) => {
app.get('/newcall', OIDCAuth, (req, res) => {
if (!OIDC.enabled && hostCfg.protected) {
const ip = getIP(req);
if (allowedIP(ip)) {
@@ -509,12 +514,12 @@ app.get(['/newcall'], OIDCAuth, (req, res) => {
res.redirect('/login');
}
} else {
res.sendFile(views.newCall);
htmlInjector.injectHtml(views.newCall, res);
}
});
// Get stats endpoint
app.get(['/stats'], (req, res) => {
app.get('/stats', (req, res) => {
//log.debug('Send stats', statsData);
res.send(statsData);
});
@@ -591,7 +596,9 @@ app.get('/join/', async (req, res) => {
} catch (err) {
// Invalid token
log.error('Direct Join JWT error', err.message);
return hostCfg.protected || hostCfg.user_auth ? res.sendFile(views.login) : res.sendFile(views.landing);
return hostCfg.protected || hostCfg.user_auth
? htmlInjector.injectHtml(views.login, res)
: htmlInjector.injectHtml(views.landing, res);
}
}
@@ -612,9 +619,9 @@ app.get('/join/', async (req, res) => {
// Check if peer authenticated or valid
if (room && (hostCfg.authenticated || isPeerValid)) {
// only room mandatory
return res.sendFile(views.client);
return htmlInjector.injectHtml(views.client, res);
} else {
return res.sendFile(views.login);
return htmlInjector.injectHtml(views.login, res);
}
}
});
@@ -637,7 +644,7 @@ app.get('/join/:roomId', function (req, res) {
const allowRoomAccess = isAllowedRoomAccess('/join/:roomId', req, hostCfg, peers, roomId);
if (allowRoomAccess) {
res.sendFile(views.client);
htmlInjector.injectHtml(views.client, res);
} else {
!OIDC.enabled && hostCfg.protected ? res.redirect('/login') : res.redirect('/');
}
@@ -651,13 +658,13 @@ app.get('/join/*', function (req, res) {
// Login
app.get(['/login'], (req, res) => {
if (hostCfg.protected || hostCfg.user_auth) {
return res.sendFile(views.login);
return htmlInjector.injectHtml(views.login, res);
}
res.redirect('/');
});
// Logged
app.get(['/logged'], (req, res) => {
app.get('/logged', (req, res) => {
const ip = getIP(req);
if (allowedIP(ip)) {
res.redirect('/');
@@ -670,7 +677,7 @@ app.get(['/logged'], (req, res) => {
/* AXIOS */
// handle login on host protected
app.post(['/login'], (req, res) => {
app.post('/login', (req, res) => {
//
const ip = getIP(req);
log.debug(`Request login to host from: ${ip}`, req.body);
@@ -733,7 +740,7 @@ app.get('/:roomId', (req, res) => {
*/
// request stats list
app.get([`${apiBasePath}/stats`], (req, res) => {
app.get(`${apiBasePath}/stats`, (req, res) => {
// Check if endpoint allowed
if (api_disabled.includes('stats')) {
return res.status(403).json({
@@ -769,7 +776,7 @@ app.get([`${apiBasePath}/stats`], (req, res) => {
});
// request token endpoint
app.post([`${apiBasePath}/token`], (req, res) => {
app.post(`${apiBasePath}/token`, (req, res) => {
// Check if endpoint allowed
if (api_disabled.includes('token')) {
return res.status(403).json({
@@ -798,7 +805,7 @@ app.post([`${apiBasePath}/token`], (req, res) => {
});
// request meetings list
app.get([`${apiBasePath}/meetings`], (req, res) => {
app.get(`${apiBasePath}/meetings`, (req, res) => {
// Check if endpoint allowed
if (api_disabled.includes('meetings')) {
return res.status(403).json({
@@ -827,7 +834,7 @@ app.get([`${apiBasePath}/meetings`], (req, res) => {
});
// API request meeting room endpoint
app.post([`${apiBasePath}/meeting`], (req, res) => {
app.post(`${apiBasePath}/meeting`, (req, res) => {
// Check if endpoint allowed
if (api_disabled.includes('meeting')) {
return res.status(403).json({
@@ -853,7 +860,7 @@ app.post([`${apiBasePath}/meeting`], (req, res) => {
});
// API request join room endpoint
app.post([`${apiBasePath}/join`], (req, res) => {
app.post(`${apiBasePath}/join`, (req, res) => {
// Check if endpoint allowed
if (api_disabled.includes('join')) {
return res.status(403).json({