[mirotalksfu] - add whoAreYou and api_room_exists endpoint
هذا الالتزام موجود في:
@@ -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.5.90
|
* @version 1.5.91
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -144,6 +144,7 @@ const hostCfg = {
|
|||||||
users_api_rooms_allowed: config.host.users_api_rooms_allowed,
|
users_api_rooms_allowed: config.host.users_api_rooms_allowed,
|
||||||
users_api_endpoint: config.host.users_api_endpoint,
|
users_api_endpoint: config.host.users_api_endpoint,
|
||||||
users_api_secret_key: config.host.users_api_secret_key,
|
users_api_secret_key: config.host.users_api_secret_key,
|
||||||
|
api_room_exists: config.host.api_room_exists,
|
||||||
users: config.host.users,
|
users: config.host.users,
|
||||||
authenticated: !config.host.protected,
|
authenticated: !config.host.protected,
|
||||||
};
|
};
|
||||||
@@ -228,6 +229,7 @@ const views = {
|
|||||||
privacy: path.join(__dirname, '../../', 'public/views/privacy.html'),
|
privacy: path.join(__dirname, '../../', 'public/views/privacy.html'),
|
||||||
room: path.join(__dirname, '../../', 'public/views/Room.html'),
|
room: path.join(__dirname, '../../', 'public/views/Room.html'),
|
||||||
rtmpStreamer: path.join(__dirname, '../../', 'public/views/RtmpStreamer.html'),
|
rtmpStreamer: path.join(__dirname, '../../', 'public/views/RtmpStreamer.html'),
|
||||||
|
whoAreYou: path.join(__dirname, '../../', 'public/views/whoAreYou.html'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const authHost = new Host(); // Authenticated IP by Login
|
const authHost = new Host(); // Authenticated IP by Login
|
||||||
@@ -550,7 +552,7 @@ function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// join room by id
|
// join room by id
|
||||||
app.get('/join/:roomId', (req, res) => {
|
app.get('/join/:roomId', async (req, res) => {
|
||||||
//
|
//
|
||||||
const { roomId } = req.params;
|
const { roomId } = req.params;
|
||||||
|
|
||||||
@@ -567,8 +569,17 @@ function startServer() {
|
|||||||
const allowRoomAccess = isAllowedRoomAccess('/join/:roomId', req, hostCfg, roomList, roomId);
|
const allowRoomAccess = isAllowedRoomAccess('/join/:roomId', req, hostCfg, roomList, roomId);
|
||||||
|
|
||||||
if (allowRoomAccess) {
|
if (allowRoomAccess) {
|
||||||
|
// Protect unauthorized room access...
|
||||||
|
if (!OIDC.enabled && hostCfg.protected && hostCfg.users_from_db) {
|
||||||
|
const roomExists = await roomExistsForUser(roomId);
|
||||||
|
return roomExists ? res.sendFile(views.room) : res.redirect('/login');
|
||||||
|
}
|
||||||
res.sendFile(views.room);
|
res.sendFile(views.room);
|
||||||
} else {
|
} else {
|
||||||
|
// Who are you?
|
||||||
|
if (!OIDC.enabled && hostCfg.protected && hostCfg.users_from_db) {
|
||||||
|
return res.redirect('/whoAreYou/' + roomId);
|
||||||
|
}
|
||||||
!OIDC.enabled && hostCfg.protected ? res.redirect('/login') : res.redirect('/');
|
!OIDC.enabled && hostCfg.protected ? res.redirect('/login') : res.redirect('/');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -600,6 +611,11 @@ function startServer() {
|
|||||||
res.send(stats);
|
res.send(stats);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle who are you: Presenter or Guest
|
||||||
|
app.get(['/whoAreYou/:roomId'], (req, res) => {
|
||||||
|
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) => {
|
||||||
res.sendFile(views.login);
|
res.sendFile(views.login);
|
||||||
@@ -2952,6 +2968,31 @@ function startServer() {
|
|||||||
return allowRoomAccess;
|
return allowRoomAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function roomExistsForUser(room) {
|
||||||
|
if (hostCfg.protected || hostCfg.user_auth) {
|
||||||
|
// Check if passed room exists
|
||||||
|
if (hostCfg.users_from_db && hostCfg.api_room_exists) {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
hostCfg.api_room_exists,
|
||||||
|
{
|
||||||
|
room: room,
|
||||||
|
api_secret_key: hostCfg.users_api_secret_key,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
log.debug('AXIOS roomExistsForUser', { room: room, exists: true });
|
||||||
|
return response.data && response.data.message === true;
|
||||||
|
} catch (error) {
|
||||||
|
log.error('AXIOS roomExistsForUser error', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getUserAllowedRooms(username, password) {
|
async function getUserAllowedRooms(username, password) {
|
||||||
// Gel user allowed rooms from db...
|
// Gel user allowed rooms from db...
|
||||||
if (hostCfg.protected && hostCfg.users_from_db && hostCfg.users_api_rooms_allowed) {
|
if (hostCfg.protected && hostCfg.users_from_db && hostCfg.users_api_rooms_allowed) {
|
||||||
@@ -3018,10 +3059,10 @@ function startServer() {
|
|||||||
timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds)
|
timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
log.debug('AXIOS isRoomAllowedForUser', { room: room, allowed: true });
|
||||||
return response.data && response.data.message === true;
|
return response.data && response.data.message === true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('AXIOS isRoomAllowedForUserDb error', error.message);
|
log.error('AXIOS isRoomAllowedForUser error', error.message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,9 +175,11 @@ module.exports = {
|
|||||||
users_api_endpoint: 'http://localhost:9000/api/v1/user/isAuth',
|
users_api_endpoint: 'http://localhost:9000/api/v1/user/isAuth',
|
||||||
users_api_room_allowed: 'http://localhost:9000/api/v1/user/isRoomAllowed',
|
users_api_room_allowed: 'http://localhost:9000/api/v1/user/isRoomAllowed',
|
||||||
users_api_rooms_allowed: 'http://localhost:9000/api/v1/user/roomsAllowed',
|
users_api_rooms_allowed: 'http://localhost:9000/api/v1/user/roomsAllowed',
|
||||||
|
api_room_exists: 'http://localhost:9000/api/v1/room/exists',
|
||||||
//users_api_endpoint: 'https://webrtc.mirotalk.com/api/v1/user/isAuth',
|
//users_api_endpoint: 'https://webrtc.mirotalk.com/api/v1/user/isAuth',
|
||||||
//users_api_room_allowed: 'https://webrtc.mirotalk.com/api/v1/user/isRoomAllowed',
|
//users_api_room_allowed: 'https://webrtc.mirotalk.com/api/v1/user/isRoomAllowed',
|
||||||
//users_api_rooms_allowed: 'https://webrtc.mirotalk.com/api/v1/user/roomsAllowed',
|
//users_api_rooms_allowed: 'https://webrtc.mirotalk.com/api/v1/user/roomsAllowed',
|
||||||
|
//api_room_exists: 'https://webrtc.mirotalk.com//api/v1/room/exists',
|
||||||
users_api_secret_key: 'mirotalkweb_default_secret',
|
users_api_secret_key: 'mirotalkweb_default_secret',
|
||||||
users: [
|
users: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.5.90",
|
"version": "1.5.91",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function login() {
|
|||||||
|
|
||||||
// http://localhost:3010/join/test
|
// http://localhost:3010/join/test
|
||||||
const pathParts = window.location.pathname.split('/');
|
const pathParts = window.location.pathname.split('/');
|
||||||
const roomPath = pathParts[pathParts.length - 1];
|
const roomPath = filterXSS(pathParts[pathParts.length - 1]);
|
||||||
|
|
||||||
if (username && password) {
|
if (username && password) {
|
||||||
axios
|
axios
|
||||||
|
|||||||
@@ -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.5.90
|
* @version 1.5.91
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -4500,7 +4500,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.5.90',
|
title: 'WebRTC SFU v1.5.91',
|
||||||
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.5.90
|
* @version 1.5.91
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
17
public/js/WhoAreYou.js
Normal file
17
public/js/WhoAreYou.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
console.log(window.location);
|
||||||
|
|
||||||
|
const presenterLoginBtn = document.getElementById('presenterLoginButton');
|
||||||
|
const guestJoinRoomBtn = document.getElementById('guestJoinRoomButton');
|
||||||
|
|
||||||
|
const pathParts = window.location.pathname.split('/');
|
||||||
|
const roomPath = filterXSS(pathParts[pathParts.length - 1]);
|
||||||
|
|
||||||
|
presenterLoginBtn.onclick = (e) => {
|
||||||
|
window.location.href = '/login';
|
||||||
|
};
|
||||||
|
|
||||||
|
guestJoinRoomBtn.onclick = (e) => {
|
||||||
|
window.location.href = '/join/' + roomPath;
|
||||||
|
};
|
||||||
202
public/views/whoAreYou.html
Executable file
202
public/views/whoAreYou.html
Executable file
@@ -0,0 +1,202 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="no-js">
|
||||||
|
<head>
|
||||||
|
<!-- Title and Icon -->
|
||||||
|
|
||||||
|
<title id="title">MiroTalk SFU - Who are you.</title>
|
||||||
|
<link id="icon" rel="shortcut icon" href="../images/logo.svg" />
|
||||||
|
<link id="appleTouchIcon" rel="apple-touch-icon" href="../images/logo.svg" />
|
||||||
|
|
||||||
|
<!-- Meta Information -->
|
||||||
|
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<meta
|
||||||
|
id="description"
|
||||||
|
name="description"
|
||||||
|
content="MiroTalk SFU powered by WebRTC and mediasoup, Real-time Simple Secure Fast video calls, messaging and screen sharing capabilities in the browser."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
id="keywords"
|
||||||
|
name="keywords"
|
||||||
|
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" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="../css/landing.css" />
|
||||||
|
|
||||||
|
<!-- Js scripts -->
|
||||||
|
|
||||||
|
<script defer src="../js/Brand.js"></script>
|
||||||
|
<script async src="../js/Umami.js"></script>
|
||||||
|
<script async src="../js/WhoAreYou.js"></script>
|
||||||
|
|
||||||
|
<!-- xss -->
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/xss/dist/xss.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/animejs@3.0.1/lib/anime.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/scrollreveal@4.0.0/dist/scrollreveal.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="is-boxed has-animations">
|
||||||
|
<div class="body-wrap">
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="container">
|
||||||
|
<div class="site-header-inner">
|
||||||
|
<div class="brand header-brand">
|
||||||
|
<h1 class="m-0"></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container">
|
||||||
|
<div class="hero-inner">
|
||||||
|
<div class="hero-copy">
|
||||||
|
<h1 class="hero-title mt-0">Who are you? <br /></h1>
|
||||||
|
<p class="hero-paragraph">
|
||||||
|
Please log in if you are the Presenter. Otherwise, kindly wait for the presenter to
|
||||||
|
join.
|
||||||
|
</p>
|
||||||
|
<div class="hero-cta">
|
||||||
|
<a id="presenterLoginButton" class="button button-primary pulse">LOGIN</a>
|
||||||
|
<a id="guestJoinRoomButton" class="button button-primary pulse">JOIN ROOM</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-figure anime-element">
|
||||||
|
<svg class="placeholder" width="528" height="396" viewBox="0 0 528 396">
|
||||||
|
<rect width="528" height="396" style="fill: transparent" />
|
||||||
|
</svg>
|
||||||
|
<div class="hero-figure-box hero-figure-box-01" data-rotation="45deg"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-02" data-rotation="-45deg"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-03" data-rotation="0deg"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-04" data-rotation="-135deg"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-05"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-06"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-07"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-08" data-rotation="-22deg"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-09" data-rotation="-52deg"></div>
|
||||||
|
<div class="hero-figure-box hero-figure-box-10" data-rotation="-50deg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer id="footer" class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="site-footer-inner">
|
||||||
|
<div class="brand footer-brand"></div>
|
||||||
|
<ul class="footer-links list-reset">
|
||||||
|
<li>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/privacy">Privacy Policy</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://sfu.mirotalk.com/api/v1/docs/">Rest API</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Contact:
|
||||||
|
<a target="_blank" href="https://www.linkedin.com/in/miroslav-pejic-976a07101/"
|
||||||
|
>Miroslav Pejic</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="footer-social-links list-reset">
|
||||||
|
<li class="footer-social-icon">
|
||||||
|
<a target="_blank" href="https://discord.gg/rgGYfeYW3N">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Forum</title>
|
||||||
|
<path
|
||||||
|
d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"
|
||||||
|
fill="#0270D7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-social-icon">
|
||||||
|
<a target="_blank" href="https://www.facebook.com/mirotalk">
|
||||||
|
<span class="screen-reader-text">Facebook</span>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M6.023 16L6 9H3V6h3V4c0-2.7 1.672-4 4.08-4 1.153 0 2.144.086 2.433.124v2.821h-1.67c-1.31 0-1.563.623-1.563 1.536V6H13l-1 3H9.28v7H6.023z"
|
||||||
|
fill="#0270D7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-social-icon">
|
||||||
|
<a target="_blank" href="https://www.youtube.com/watch?v=_IVn2aINYww">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M21.582,6.186c-0.23-0.86-0.908-1.538-1.768-1.768C18.254,4,12,4,12,4S5.746,4,4.186,4.418 c-0.86,0.23-1.538,0.908-1.768,1.768C2,7.746,2,12,2,12s0,4.254,0.418,5.814c0.23,0.86,0.908,1.538,1.768,1.768 C5.746,20,12,20,12,20s6.254,0,7.814-0.418c0.861-0.23,1.538-0.908,1.768-1.768C22,16.254,22,12,22,12S22,7.746,21.582,6.186z M10,15.464V8.536L16,12L10,15.464z"
|
||||||
|
fill="#0270D7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="footer-social-icon">
|
||||||
|
<a target="_blank" href="mailto:miroslav.pejic.85@gmail.com">
|
||||||
|
<span class="screen-reader-text">Google</span>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M7.9 7v2.4H12c-.2 1-1.2 3-4 3-2.4 0-4.3-2-4.3-4.4 0-2.4 2-4.4 4.3-4.4 1.4 0 2.3.6 2.8 1.1l1.9-1.8C11.5 1.7 9.9 1 8 1 4.1 1 1 4.1 1 8s3.1 7 7 7c4 0 6.7-2.8 6.7-6.8 0-.5 0-.8-.1-1.2H7.9z"
|
||||||
|
fill="#0270D7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="github-button"
|
||||||
|
href="https://github.com/sponsors/miroslavpejic85"
|
||||||
|
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||||
|
data-icon="octicon-heart"
|
||||||
|
data-size="large"
|
||||||
|
aria-label="Sponsor @miroslavpejic85 on GitHub"
|
||||||
|
>Sponsor</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="github-button"
|
||||||
|
href="https://github.com/miroslavpejic85/mirotalksfu"
|
||||||
|
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||||
|
data-size="large"
|
||||||
|
data-show-count="true"
|
||||||
|
aria-label="Star miroslavpejic85/mirotalk on GitHub"
|
||||||
|
>Star</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="footer-copyright">© 2024 MiroTalk SFU, all rights reserved</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script defer src="../js/Landing.js"></script>
|
||||||
|
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم