[mirotalksfu] - add HtmlInjector
هذا الالتزام موجود في:
95
app/src/HtmlInjector.js
Normal file
95
app/src/HtmlInjector.js
Normal file
@@ -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;
|
||||||
@@ -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 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
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @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 ServerApi = require('./ServerApi');
|
||||||
const Logger = require('./Logger');
|
const Logger = require('./Logger');
|
||||||
const Validator = require('./Validator');
|
const Validator = require('./Validator');
|
||||||
|
const HtmlInjector = require('./HtmlInjector');
|
||||||
const log = new Logger('Server');
|
const log = new Logger('Server');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const swaggerUi = require('swagger-ui-express');
|
const swaggerUi = require('swagger-ui-express');
|
||||||
@@ -237,6 +238,7 @@ if (serverRecordingEnabled) {
|
|||||||
|
|
||||||
// html views
|
// html views
|
||||||
const views = {
|
const views = {
|
||||||
|
html: path.join(__dirname, '../../public/views'),
|
||||||
about: path.join(__dirname, '../../', 'public/views/about.html'),
|
about: path.join(__dirname, '../../', 'public/views/about.html'),
|
||||||
landing: path.join(__dirname, '../../', 'public/views/landing.html'),
|
landing: path.join(__dirname, '../../', 'public/views/landing.html'),
|
||||||
login: path.join(__dirname, '../../', 'public/views/login.html'),
|
login: path.join(__dirname, '../../', 'public/views/login.html'),
|
||||||
@@ -249,6 +251,10 @@ const views = {
|
|||||||
whoAreYou: path.join(__dirname, '../../', 'public/views/whoAreYou.html'),
|
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 authHost = new Host(); // Authenticated IP by Login
|
||||||
|
|
||||||
const roomList = new Map(); // All Rooms
|
const roomList = new Map(); // All Rooms
|
||||||
@@ -457,20 +463,19 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// main page
|
// main page
|
||||||
app.get(['/'], OIDCAuth, (req, res) => {
|
app.get('/', OIDCAuth, (req, res) => {
|
||||||
//log.debug('/ - hostCfg ----->', hostCfg);
|
//log.debug('/ - hostCfg ----->', hostCfg);
|
||||||
|
|
||||||
if (!OIDC.enabled && hostCfg.protected) {
|
if (!OIDC.enabled && hostCfg.protected) {
|
||||||
const ip = getIP(req);
|
const ip = getIP(req);
|
||||||
if (allowedIP(ip)) {
|
if (allowedIP(ip)) {
|
||||||
res.sendFile(views.landing);
|
htmlInjector.injectHtml(views.landing, res);
|
||||||
hostCfg.authenticated = true;
|
hostCfg.authenticated = true;
|
||||||
} else {
|
} else {
|
||||||
hostCfg.authenticated = false;
|
hostCfg.authenticated = false;
|
||||||
res.redirect('/login');
|
res.redirect('/login');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.sendFile(views.landing);
|
return htmlInjector.injectHtml(views.landing, res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -483,7 +488,7 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// set new room name and join
|
// set new room name and join
|
||||||
app.get(['/newroom'], OIDCAuth, (req, res) => {
|
app.get('/newroom', OIDCAuth, (req, res) => {
|
||||||
//log.info('/newroom - hostCfg ----->', hostCfg);
|
//log.info('/newroom - hostCfg ----->', hostCfg);
|
||||||
|
|
||||||
if (!OIDC.enabled && hostCfg.protected) {
|
if (!OIDC.enabled && hostCfg.protected) {
|
||||||
@@ -496,12 +501,12 @@ function startServer() {
|
|||||||
res.redirect('/login');
|
res.redirect('/login');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.sendFile(views.newRoom);
|
htmlInjector.injectHtml(views.newRoom, res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if room active (exists)
|
// Check if room active (exists)
|
||||||
app.post(['/isRoomActive'], (req, res) => {
|
app.post('/isRoomActive', (req, res) => {
|
||||||
const { roomId } = checkXSS(req.body);
|
const { roomId } = checkXSS(req.body);
|
||||||
|
|
||||||
if (roomId && (hostCfg.protected || hostCfg.user_auth)) {
|
if (roomId && (hostCfg.protected || hostCfg.user_auth)) {
|
||||||
@@ -571,8 +576,8 @@ function startServer() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Direct Join JWT error', { error: err.message, token: token });
|
log.error('Direct Join JWT error', { error: err.message, token: token });
|
||||||
return hostCfg.protected || hostCfg.user_auth
|
return hostCfg.protected || hostCfg.user_auth
|
||||||
? res.sendFile(views.login)
|
? htmlInjector.injectHtml(views.login, res)
|
||||||
: res.sendFile(views.landing);
|
: htmlInjector.injectHtml(views.landing, res);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const allowRoomAccess = isAllowedRoomAccess('/join/params', req, hostCfg, roomList, room);
|
const allowRoomAccess = isAllowedRoomAccess('/join/params', req, hostCfg, roomList, room);
|
||||||
@@ -601,9 +606,9 @@ function startServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (room && (hostCfg.authenticated || isPeerValid)) {
|
if (room && (hostCfg.authenticated || isPeerValid)) {
|
||||||
return res.sendFile(views.room);
|
return htmlInjector.injectHtml(views.room, res);
|
||||||
} else {
|
} 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) {
|
if (!OIDC.enabled && hostCfg.protected && hostCfg.users_from_db) {
|
||||||
const roomExists = await roomExistsForUser(roomId);
|
const roomExists = await roomExistsForUser(roomId);
|
||||||
log.debug('/join/:roomId exists from API endpoint', roomExists);
|
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
|
// 2. Protect room access with configuration check
|
||||||
if (!OIDC.enabled && hostCfg.protected && !hostCfg.users_from_db) {
|
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)),
|
(user) => user.allowed_rooms && (user.allowed_rooms.includes(roomId) || roomList.has(roomId)),
|
||||||
);
|
);
|
||||||
log.debug('/join/:roomId exists from config allowed rooms', roomExists);
|
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 {
|
} else {
|
||||||
// Who are you?
|
// Who are you?
|
||||||
!OIDC.enabled && hostCfg.protected ? res.redirect('/whoAreYou/' + roomId) : res.redirect('/');
|
!OIDC.enabled && hostCfg.protected ? res.redirect('/whoAreYou/' + roomId) : res.redirect('/');
|
||||||
@@ -655,42 +660,42 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// if not allow video/audio
|
// if not allow video/audio
|
||||||
app.get(['/permission'], (req, res) => {
|
app.get('/permission', (req, res) => {
|
||||||
res.sendFile(views.permission);
|
res.sendFile(views.permission);
|
||||||
});
|
});
|
||||||
|
|
||||||
// privacy policy
|
// privacy policy
|
||||||
app.get(['/privacy'], (req, res) => {
|
app.get('/privacy', (req, res) => {
|
||||||
res.sendFile(views.privacy);
|
res.sendFile(views.privacy);
|
||||||
});
|
});
|
||||||
|
|
||||||
// mirotalk about
|
// mirotalk about
|
||||||
app.get(['/about'], (req, res) => {
|
app.get('/about', (req, res) => {
|
||||||
res.sendFile(views.about);
|
res.sendFile(views.about);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get stats endpoint
|
// Get stats endpoint
|
||||||
app.get(['/stats'], (req, res) => {
|
app.get('/stats', (req, res) => {
|
||||||
const stats = config.stats ? config.stats : defaultStats;
|
const stats = config.stats ? config.stats : defaultStats;
|
||||||
// log.debug('Send stats', stats);
|
// log.debug('Send stats', stats);
|
||||||
res.send(stats);
|
res.send(stats);
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle who are you: Presenter or Guest
|
// handle who are you: Presenter or Guest
|
||||||
app.get(['/whoAreYou/:roomId'], (req, res) => {
|
app.get('/whoAreYou/:roomId', (req, res) => {
|
||||||
res.sendFile(views.whoAreYou);
|
res.sendFile(views.whoAreYou);
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle login if user_auth enabled
|
// handle login if user_auth enabled
|
||||||
app.get(['/login'], (req, res) => {
|
app.get('/login', (req, res) => {
|
||||||
if (hostCfg.protected || hostCfg.user_auth) {
|
if (hostCfg.protected || hostCfg.user_auth) {
|
||||||
return res.sendFile(views.login);
|
return htmlInjector.injectHtml(views.login, res);
|
||||||
}
|
}
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle logged on host protected
|
// handle logged on host protected
|
||||||
app.get(['/logged'], (req, res) => {
|
app.get('/logged', (req, res) => {
|
||||||
const ip = getIP(req);
|
const ip = getIP(req);
|
||||||
if (allowedIP(ip)) {
|
if (allowedIP(ip)) {
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
@@ -706,7 +711,7 @@ function startServer() {
|
|||||||
// ####################################################
|
// ####################################################
|
||||||
|
|
||||||
// handle login on host protected
|
// handle login on host protected
|
||||||
app.post(['/login'], async (req, res) => {
|
app.post('/login', async (req, res) => {
|
||||||
const ip = getIP(req);
|
const ip = getIP(req);
|
||||||
log.debug(`Request login to host from: ${ip}`, req.body);
|
log.debug(`Request login to host from: ${ip}`, req.body);
|
||||||
|
|
||||||
@@ -753,7 +758,7 @@ function startServer() {
|
|||||||
// KEEP RECORDING ON SERVER DIR
|
// KEEP RECORDING ON SERVER DIR
|
||||||
// ####################################################
|
// ####################################################
|
||||||
|
|
||||||
app.post(['/recSync'], (req, res) => {
|
app.post('/recSync', (req, res) => {
|
||||||
// Store recording...
|
// Store recording...
|
||||||
if (serverRecordingEnabled) {
|
if (serverRecordingEnabled) {
|
||||||
//
|
//
|
||||||
@@ -940,7 +945,7 @@ function startServer() {
|
|||||||
// REST API
|
// REST API
|
||||||
// ####################################################
|
// ####################################################
|
||||||
|
|
||||||
app.get([restApi.basePath + '/stats'], (req, res) => {
|
app.get(restApi.basePath + '/stats', (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Check if endpoint allowed
|
// Check if endpoint allowed
|
||||||
if (restApi.allowed && !restApi.allowed.stats) {
|
if (restApi.allowed && !restApi.allowed.stats) {
|
||||||
@@ -985,7 +990,7 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// request meetings list
|
// request meetings list
|
||||||
app.get([restApi.basePath + '/meetings'], (req, res) => {
|
app.get(restApi.basePath + '/meetings', (req, res) => {
|
||||||
// Check if endpoint allowed
|
// Check if endpoint allowed
|
||||||
if (restApi.allowed && !restApi.allowed.meetings) {
|
if (restApi.allowed && !restApi.allowed.meetings) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
@@ -1014,7 +1019,7 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// request meeting room endpoint
|
// request meeting room endpoint
|
||||||
app.post([restApi.basePath + '/meeting'], (req, res) => {
|
app.post(restApi.basePath + '/meeting', (req, res) => {
|
||||||
// Check if endpoint allowed
|
// Check if endpoint allowed
|
||||||
if (restApi.allowed && !restApi.allowed.meeting) {
|
if (restApi.allowed && !restApi.allowed.meeting) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
@@ -1043,7 +1048,7 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// request join room endpoint
|
// request join room endpoint
|
||||||
app.post([restApi.basePath + '/join'], (req, res) => {
|
app.post(restApi.basePath + '/join', (req, res) => {
|
||||||
// Check if endpoint allowed
|
// Check if endpoint allowed
|
||||||
if (restApi.allowed && !restApi.allowed.join) {
|
if (restApi.allowed && !restApi.allowed.join) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
@@ -1072,7 +1077,7 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// request token endpoint
|
// request token endpoint
|
||||||
app.post([restApi.basePath + '/token'], (req, res) => {
|
app.post(restApi.basePath + '/token', (req, res) => {
|
||||||
// Check if endpoint allowed
|
// Check if endpoint allowed
|
||||||
if (restApi.allowed && !restApi.allowed.token) {
|
if (restApi.allowed && !restApi.allowed.token) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.7.17",
|
"version": "1.7.18",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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 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
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @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,
|
imageUrl: image.about,
|
||||||
customClass: { image: 'img-about' },
|
customClass: { image: 'img-about' },
|
||||||
position: 'center',
|
position: 'center',
|
||||||
title: 'WebRTC SFU v1.7.17',
|
title: 'WebRTC SFU v1.7.18',
|
||||||
html: `
|
html: `
|
||||||
<br />
|
<br />
|
||||||
<div id="about">
|
<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 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
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.7.17
|
* @version 1.7.18
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
||||||
|
|||||||
@@ -28,16 +28,12 @@
|
|||||||
|
|
||||||
<!-- https://ogp.me -->
|
<!-- https://ogp.me -->
|
||||||
|
|
||||||
<meta id="ogType" property="og:type" content="app-webrtc" />
|
<meta property="og:type" content="{{OG_TYPE}}" />
|
||||||
<meta id="ogSiteName" property="og:site_name" content="MiroTalk SFU" />
|
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
|
||||||
<meta id="ogTitle" property="og:title" content="Click the link to make a call." />
|
<meta property="og:title" content="{{OG_TITLE}}" />
|
||||||
<meta
|
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
|
||||||
id="ogDescription"
|
<meta property="og:image" content="{{OG_IMAGE}}" />
|
||||||
property="og:description"
|
<meta property="og:url" content="{{OG_URL}}" />
|
||||||
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 -->
|
<!-- StyleSheet -->
|
||||||
<link rel="stylesheet" href="../css/Root.css" />
|
<link rel="stylesheet" href="../css/Root.css" />
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="../css/Root.css" />
|
<link rel="stylesheet" href="../css/Root.css" />
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
||||||
|
|||||||
@@ -26,16 +26,12 @@
|
|||||||
|
|
||||||
<!-- https://ogp.me -->
|
<!-- https://ogp.me -->
|
||||||
|
|
||||||
<meta id="ogType" property="og:type" content="app-webrtc" />
|
<meta property="og:type" content="{{OG_TYPE}}" />
|
||||||
<meta id="ogSiteName" property="og:site_name" content="MiroTalk SFU" />
|
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
|
||||||
<meta id="ogTitle" property="og:title" content="Click the link to make a call." />
|
<meta property="og:title" content="{{OG_TITLE}}" />
|
||||||
<meta
|
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
|
||||||
id="ogDescription"
|
<meta property="og:image" content="{{OG_IMAGE}}" />
|
||||||
property="og:description"
|
<meta property="og:url" content="{{OG_URL}}" />
|
||||||
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
|
|||||||
@@ -26,16 +26,12 @@
|
|||||||
|
|
||||||
<!-- https://ogp.me -->
|
<!-- https://ogp.me -->
|
||||||
|
|
||||||
<meta id="ogType" property="og:type" content="app-webrtc" />
|
<meta property="og:type" content="{{OG_TYPE}}" />
|
||||||
<meta id="ogSiteName" property="og:site_name" content="MiroTalk SFU" />
|
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
|
||||||
<meta id="ogTitle" property="og:title" content="Click the link to make a call." />
|
<meta property="og:title" content="{{OG_TITLE}}" />
|
||||||
<meta
|
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
|
||||||
id="ogDescription"
|
<meta property="og:image" content="{{OG_IMAGE}}" />
|
||||||
property="og:description"
|
<meta property="og:url" content="{{OG_URL}}" />
|
||||||
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
|
|||||||
@@ -26,16 +26,12 @@
|
|||||||
|
|
||||||
<!-- https://ogp.me -->
|
<!-- https://ogp.me -->
|
||||||
|
|
||||||
<meta id="ogType" property="og:type" content="app-webrtc" />
|
<meta property="og:type" content="{{OG_TYPE}}" />
|
||||||
<meta id="ogSiteName" property="og:site_name" content="MiroTalk SFU" />
|
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
|
||||||
<meta id="ogTitle" property="og:title" content="Click the link to make a call." />
|
<meta property="og:title" content="{{OG_TITLE}}" />
|
||||||
<meta
|
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
|
||||||
id="ogDescription"
|
<meta property="og:image" content="{{OG_IMAGE}}" />
|
||||||
property="og:description"
|
<meta property="og:url" content="{{OG_URL}}" />
|
||||||
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 -->
|
<!-- StyleSheet -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
||||||
|
|||||||
@@ -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"
|
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 -->
|
<!-- StyleSheet -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600" />
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم