From 91d24541025df02b134250e169e88d905e852667 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Fri, 31 Jan 2025 19:59:03 +0100 Subject: [PATCH] [mirotalkwebrtc] - make OG customizable, add Language, improvements --- backend/config.template.js | 9 ++++ backend/server.js | 13 +++-- backend/utils/HtmlInjector.js | 94 +++++++++++++++++++++++++++++++++++ frontend/css/client.css | 17 ------- frontend/css/common.css | 20 ++++++++ frontend/html/client.html | 34 +++++++------ frontend/html/home.html | 16 +++--- frontend/js/client.js | 14 +++--- frontend/js/translate.js | 44 ++++++++++++++-- package.json | 2 +- 10 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 backend/utils/HtmlInjector.js diff --git a/backend/config.template.js b/backend/config.template.js index b94032c..bf55800 100644 --- a/backend/config.template.js +++ b/backend/config.template.js @@ -1,10 +1,19 @@ 'use strict'; module.exports = { + Language: 'en', // https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes App: { Name: 'MiroTalk', Logo: '../Images/logo.png', }, + OG: { + type: 'app-webrtc', + siteName: 'MiroTalk WebRTC', + title: 'Click the link to schedule the Meeting', + description: 'MiroTalk WEB Easy Room Scheduler for Meetings & Video Conferencing.', + image: 'https://webrtc.mirotalk.com/Images/mirotalk-bro.png', + url: 'https://webrtc.mirotalk.com', + }, Author: { Email: 'miroslav.pejic.85@gmail.com', Profile: 'https://www.linkedin.com/in/miroslav-pejic-976a07101/', diff --git a/backend/server.js b/backend/server.js index b67b21f..02d56fd 100644 --- a/backend/server.js +++ b/backend/server.js @@ -9,6 +9,7 @@ const url = require('./middleware/url'); const corsOptions = require('./config/cors'); const cors = require('cors'); const compression = require('compression'); +const HtmlInjector = require('./utils/HtmlInjector'); const helmet = require('helmet'); const api = require('./routes/api'); const room = require('./routes/room'); @@ -49,6 +50,10 @@ const frontendDir = path.join(__dirname, '../', 'frontend'); const login = path.join(__dirname, '../', 'frontend/html/home.html'); const client = path.join(__dirname, '../', 'frontend/html/client.html'); +// File to cache and inject custom HTML data like OG tags and any other elements. +const filesPath = [login, client]; +const htmlInjector = new HtmlInjector(filesPath, config || null); + mongoose.set('strictQuery', true); mongoose @@ -56,8 +61,8 @@ mongoose .then(() => { const app = express(); - app.use(helmet.xssFilter()); // Enable XSS protection - app.use(helmet.noSniff()); // Enable content type sniffing prevention + app.use(helmet.xssFilter()); // Enable XSS protection + app.use(helmet.noSniff()); // Enable content type sniffing prevention app.use(cors(corsOptions())); app.use(compression()); app.use(express.static(frontendDir)); @@ -82,11 +87,11 @@ mongoose app.use(apiPath, users); app.get('/', (req, res) => { - res.sendFile(login); + htmlInjector.injectHtml(login, res); }); app.get('/client', auth, (req, res) => { - res.sendFile(client); + htmlInjector.injectHtml(client, res); }); app.get('/config', auth, (req, res) => { diff --git a/backend/utils/HtmlInjector.js b/backend/utils/HtmlInjector.js new file mode 100644 index 0000000..5dfe29a --- /dev/null +++ b/backend/utils/HtmlInjector.js @@ -0,0 +1,94 @@ +const fs = require('fs'); + +const logs = require('../common/logs'); + +const log = new logs('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 WEB', + OG_TITLE: this.config?.OG?.title || 'Click the link to schedule the Meeting', + OG_DESCRIPTION: + this.config?.OG?.description || 'MiroTalk WEB Easy Room Scheduler for Meetings & Video Conferencing.', + OG_IMAGE: this.config?.OG?.image || 'https://webrtc.mirotalk.com/Images/mirotalk-web.png', + OG_URL: this.config?.OG?.url || 'https://webrtc.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/frontend/css/client.css b/frontend/css/client.css index 5e5aa9d..c59671c 100644 --- a/frontend/css/client.css +++ b/frontend/css/client.css @@ -889,23 +889,6 @@ tr:nth-child(even) { background: transparent; } -/* google translate */ -.skiptranslate iframe, -.goog-te-banner-frame.skiptranslate, -.VIpgJd-ZVi9od-aZ2wEe-wOHMyf, -.VIpgJd-ZVi9od-aZ2wEe-OiiCO, -#goog-gt-tt, -#goog-gt-tt *, -#goog-gt-vt, -#goog-gt-vt * { - display: none !important; -} - -#google_translate_element select { - background: var(--secondary-color) !important; - color: var(--text-color) !important; -} - .fadeIn { -webkit-animation: fadeIn ease-in 1; -moz-animation: fadeIn ease-in 1; diff --git a/frontend/css/common.css b/frontend/css/common.css index 2bbb069..f59fd6c 100644 --- a/frontend/css/common.css +++ b/frontend/css/common.css @@ -19,3 +19,23 @@ ::-webkit-scrollbar-track { background: #1a1b1f; } + +/*-------------------------------------------------------------- +# Google Translate +--------------------------------------------------------------*/ + +.skiptranslate iframe, +.goog-te-banner-frame.skiptranslate, +.VIpgJd-ZVi9od-aZ2wEe-wOHMyf, +.VIpgJd-ZVi9od-aZ2wEe-OiiCO, +#goog-gt-tt, +#goog-gt-tt *, +#goog-gt-vt, +#goog-gt-vt * { + display: none !important; +} + +#google_translate_element select { + background: var(--secondary-color) !important; + color: var(--text-color) !important; +} diff --git a/frontend/html/client.html b/frontend/html/client.html index 4592ac4..f995493 100644 --- a/frontend/html/client.html +++ b/frontend/html/client.html @@ -2,6 +2,8 @@ MiroTalk WebRTC admin + + @@ -9,17 +11,17 @@ - - - - + - - + + + + + + - @@ -31,8 +33,6 @@ - +
diff --git a/frontend/js/client.js b/frontend/js/client.js index bbc6877..ebad8bf 100644 --- a/frontend/js/client.js +++ b/frontend/js/client.js @@ -9,7 +9,7 @@ * @license For private project or commercial purposes contact us at: license.mirotalk@gmail.com or purchase it directly via Code Canyon: * @license https://codecanyon.net/item/a-selfhosted-mirotalks-webrtc-rooms-scheduler-server/42643313 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.1.36 + * @version 1.1.40 */ const userAgent = navigator.userAgent; @@ -570,10 +570,10 @@ function getRow(obj) { const isC2C = obj.type == 'C2C' ? 'selected' : ''; const isBRO = obj.type == 'BRO' ? 'selected' : ''; - const optionP2P = config.MiroTalk.P2P.Visible ? `` : ''; - const optionSFU = config.MiroTalk.SFU.Visible ? `` : ''; - const optionC2C = config.MiroTalk.C2C.Visible ? `` : ''; - const optionBRO = config.MiroTalk.BRO.Visible ? `` : ''; + const optionP2P = config.MiroTalk.P2P.Visible ? `` : ''; + const optionSFU = config.MiroTalk.SFU.Visible ? `` : ''; + const optionC2C = config.MiroTalk.C2C.Visible ? `` : ''; + const optionBRO = config.MiroTalk.BRO.Visible ? `` : ''; const setRandomRoomIcon = config.BUTTONS.setRandomRoom && user.allowedRoomsALL @@ -616,11 +616,11 @@ function getRow(obj) { let rooms = ``; if (!user.allowedRoomsALL) { - rooms = ``; user.allowedRooms.forEach((room) => { const selected = obj.room === room ? 'selected' : ''; - rooms += ``; + rooms += ``; }); rooms += ``; diff --git a/frontend/js/translate.js b/frontend/js/translate.js index a213931..652e21f 100644 --- a/frontend/js/translate.js +++ b/frontend/js/translate.js @@ -1,10 +1,44 @@ 'use strict'; -const trScript = document.createElement('script'); -trScript.setAttribute('async', ''); -trScript.setAttribute('src', 'https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'); -document.head.appendChild(trScript); +function loadScript(src) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.async = true; + script.src = src; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); +} function googleTranslateElementInit() { - new google.translate.TranslateElement({ pageLanguage: 'en' }, 'google_translate_element'); + new google.translate.TranslateElement( + { + pageLanguage: 'en', + autoDisplay: false, + }, + 'google_translate_element', + ); + + const language = config?.Language || 'en'; + if (language === 'en') return; // No need to switch if default is 'en' + + const observer = new MutationObserver(() => { + const select = document.querySelector('.goog-te-combo'); + if (select) { + select.value = language; + select.dispatchEvent(new Event('change')); + observer.disconnect(); + } + }); + + observer.observe(document.body, { childList: true, subtree: true }); } + +(async function initGoogleTranslate() { + try { + await loadScript('https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'); + } catch (error) { + console.error('Failed to load Google Translate script:', error); + } +})(); diff --git a/package.json b/package.json index e884a9d..28c59ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalkwebrtc", - "version": "1.1.36", + "version": "1.1.40", "description": "MiroTalk WebRTC admin", "main": "server.js", "scripts": {