[mirotalksfu] - add HtmlInjector

هذا الالتزام موجود في:
Miroslav Pejic
2025-01-29 13:29:37 +01:00
الأصل 9d56d81e03
التزام 6e573b8f43
16 ملفات معدلة مع 158 إضافات و165 حذوفات

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 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({

عرض الملف

@@ -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": {

عرض الملف

@@ -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: `
<br />
<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 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
*
*/

عرض الملف

@@ -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" />

عرض الملف

@@ -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" />

عرض الملف

@@ -28,16 +28,12 @@
<!-- 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" />
<meta property="og:type" content="{{OG_TYPE}}" />
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
<meta property="og:title" content="{{OG_TITLE}}" />
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
<meta property="og:image" content="{{OG_IMAGE}}" />
<meta property="og:url" content="{{OG_URL}}" />
<!-- StyleSheet -->
<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"
/>
<!-- 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="../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"
/>
<!-- 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" />

عرض الملف

@@ -26,16 +26,12 @@
<!-- 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" />
<meta property="og:type" content="{{OG_TYPE}}" />
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
<meta property="og:title" content="{{OG_TITLE}}" />
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
<meta property="og:image" content="{{OG_IMAGE}}" />
<meta property="og:url" content="{{OG_URL}}" />
<!-- StyleSheet -->

عرض الملف

@@ -26,16 +26,12 @@
<!-- 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" />
<meta property="og:type" content="{{OG_TYPE}}" />
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
<meta property="og:title" content="{{OG_TITLE}}" />
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
<meta property="og:image" content="{{OG_IMAGE}}" />
<meta property="og:url" content="{{OG_URL}}" />
<!-- StyleSheet -->

عرض الملف

@@ -26,16 +26,12 @@
<!-- 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" />
<meta property="og:type" content="{{OG_TYPE}}" />
<meta property="og:site_name" content="{{OG_SITE_NAME}}" />
<meta property="og:title" content="{{OG_TITLE}}" />
<meta property="og:description" content="{{OG_DESCRIPTION}}" />
<meta property="og:image" content="{{OG_IMAGE}}" />
<meta property="og:url" content="{{OG_URL}}" />
<!-- StyleSheet -->
<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"
/>
<!-- 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" />

عرض الملف

@@ -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" />

عرض الملف

@@ -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" />