From a396eedbdb8a3811887212767f98fa5c7eed67e6 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Mon, 4 Aug 2025 03:22:02 +0200 Subject: [PATCH] [mirotalksfu] - add SHOW_ACTIVE_ROOMS --- .env.template | 3 + app/src/Server.js | 49 ++++++++- app/src/ServerApi.js | 8 ++ app/src/config.template.js | 12 ++- package-lock.json | 4 +- package.json | 2 +- public/css/ActiveRooms.css | 195 ++++++++++++++++++++++++++++++++++ public/css/landing.css | 4 + public/js/ActiveRooms.js | 89 ++++++++++++++++ public/js/Brand.js | 2 +- public/js/Room.js | 4 +- public/js/RoomClient.js | 2 +- public/views/activeRooms.html | 28 +++++ 13 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 public/css/ActiveRooms.css create mode 100644 public/js/ActiveRooms.js create mode 100644 public/views/activeRooms.html diff --git a/.env.template b/.env.template index bd66b2b2..e42deb2e 100644 --- a/.env.template +++ b/.env.template @@ -281,6 +281,9 @@ COPYRIGHT_TEXT=MiroTalk SFU, all rights reserved # Copyright text # 9. UI Button Configuration # ---------------------------------------------------- +# Active Rooms +SHOW_ACTIVE_ROOMS=false # Show active rooms feature (true|false) + # Main Control Buttons SHOW_SHARE_QR=true # Show share room QR popup (true|false) SHOW_SHARE_BUTTON=true # Show share button (true|false) diff --git a/app/src/Server.js b/app/src/Server.js index 896eb2e7..1d60ff86 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -64,7 +64,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.9.18 + * @version 1.9.20 * */ @@ -298,6 +298,7 @@ const 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'), + activeRooms: path.join(__dirname, '../../', 'public/views/activeRooms.html'), newRoom: path.join(__dirname, '../../', 'public/views/newroom.html'), notFound: path.join(__dirname, '../../', 'public/views/404.html'), permission: path.join(__dirname, '../../', 'public/views/permission.html'), @@ -626,6 +627,24 @@ function startServer() { } }); + // Get Active rooms + app.get('/activeRooms', OIDCAuth, (req, res) => { + //log.info('/activeRooms'); + + if (!OIDC.enabled && hostCfg.protected) { + const ip = getIP(req); + if (allowedIP(ip)) { + res.redirect('/'); + hostCfg.authenticated = true; + } else { + hostCfg.authenticated = false; + res.redirect('/login'); + } + } else { + res.sendFile(views.activeRooms); + } + }); + // Check if room active (exists) app.post('/isRoomActive', (req, res) => { const { roomId } = checkXSS(req.body); @@ -1399,6 +1418,34 @@ function startServer() { return res.end('`Wrong signature` - Verification failed!'); }); + // #################################################### + // AUTHORIZED API IF ALLOWED + // #################################################### + + // request active rooms endpoint + app.get(restApi.basePath + '/activeRooms', (req, res) => { + // Check if endpoint allowed + if (!config.ui?.rooms?.showActive) { + return res.status(403).json({ + error: 'This endpoint has been disabled. Please contact the administrator for further information.', + }); + } + // check if user was authorized for the api call + const { host, authorization = config.api.keySecret } = req.headers; + const api = new ServerApi(host, authorization); + + // Get active rooms + const activeRooms = api.getActiveRooms(roomList); + res.json({ activeRooms: activeRooms }); + + // log.debug the output if all done + log.debug('MiroTalk get active rooms - Authorized', { + header: req.headers, + body: req.body, + activeRooms: activeRooms, + }); + }); + // not match any of page before, so 404 not found app.use((req, res) => { res.sendFile(views.notFound); diff --git a/app/src/ServerApi.js b/app/src/ServerApi.js index 65e6cc0c..39f2e5ac 100644 --- a/app/src/ServerApi.js +++ b/app/src/ServerApi.js @@ -27,6 +27,14 @@ module.exports = class ServerApi { return { timestamp, totalRooms, totalUsers }; } + getActiveRooms(roomList) { + return Array.from(roomList.entries()).map(([roomId, room]) => ({ + id: roomId, + peers: room.peers.size, + join: 'https://' + this._host + '/' + roomId, + })); + } + getMeetings(roomList) { const meetings = Array.from(roomList.entries()).map(([id, room]) => { const peers = Array.from(room.peers.values()).map( diff --git a/app/src/config.template.js b/app/src/config.template.js index a4653f95..87c05e72 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -962,10 +962,20 @@ module.exports = { * Controls all aspects of the application's visual identity, content, and metadata. * Supports environment variable overrides for deployment-specific customization. * + * ============================================== * LICENSE REQUIRED: - * ---------------- + * ============================================== * - https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 */ + rooms: { + /** + * Room Display Settings + * --------------------- + * - showActive: Show active rooms in the UI (default: false) + * https://sfu.mirotalk.com/activeRooms + */ + showActive: process.env.SHOW_ACTIVE_ROOMS === 'true', + }, brand: { /** * Application Branding diff --git a/package-lock.json b/package-lock.json index 174f4512..e978da4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mirotalksfu", - "version": "1.9.18", + "version": "1.9.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mirotalksfu", - "version": "1.9.18", + "version": "1.9.20", "license": "AGPL-3.0", "dependencies": { "@aws-sdk/client-s3": "^3.859.0", diff --git a/package.json b/package.json index 98014a8f..2b14b654 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.9.18", + "version": "1.9.20", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { diff --git a/public/css/ActiveRooms.css b/public/css/ActiveRooms.css new file mode 100644 index 00000000..1229f5cc --- /dev/null +++ b/public/css/ActiveRooms.css @@ -0,0 +1,195 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap'); + +:root { + --primary-bg: #1e2229; + --card-bg: #15181d; + --accent: #1870d7; + --accent2: #2186ff; + --text: #f6f8fa; + --card-shadow: 0 2px 8px #15181d; +} + +body { + font-family: 'Montserrat'; + background: var(--primary-bg); + margin: 0; + padding: 0; + color: var(--text); +} + +.container { + max-width: 1200px; + margin: 40px auto; + background: var(--card-bg); + border-radius: 12px; + box-shadow: var(--card-shadow); + padding: 32px; +} + +h1 { + text-align: center; + color: var(--accent2); + margin-bottom: 32px; + letter-spacing: 1px; +} + +.search-bar { + display: flex; + justify-content: center; + margin-bottom: 28px; +} + +.search-input { + padding: 10px 16px; + border-radius: 6px 0 0 6px; + border: none; + font-size: 1.1em; + background: #23262e; + color: var(--text); + outline: none; + width: 220px; +} + +.search-btn { + padding: 10px 20px; + border-radius: 0 6px 6px 0; + border: none; + background: var(--accent); + color: #fff; + font-size: 1.1em; + cursor: pointer; + font-weight: bold; + transition: background 0.2s; +} + +.search-btn:hover { + background: var(--accent2); +} + +.rooms { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 28px; + justify-content: center; +} + +.room-card { + background: var(--accent); + border-radius: 12px; + box-shadow: 0 1px 6px #15181d; + padding: 32px 24px; + min-height: 220px; + text-align: center; + transition: + box-shadow 0.2s, + transform 0.2s; + color: var(--text); + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.room-card:hover { + cursor: pointer; + transform: translateY(-4px) scale(1.03); +} + +.room-title { + font-size: 1.15em; + color: #f6f8fa; + margin-bottom: 14px; + font-weight: bold; + letter-spacing: 0.5px; + word-break: break-all; + overflow-wrap: anywhere; + max-width: 100%; +} + +.peer-count { + font-size: 2.3em; + color: #ffd700; + font-weight: bold; + margin-bottom: 8px; + margin-top: auto; + align-self: center; +} + +.peer-label { + font-size: 0.9em; + color: #f6f8fa; + opacity: 0.7; + margin-bottom: 60px; + align-self: center; +} + +.empty { + text-align: center; + color: #b3c6e0; + margin-top: 40px; + font-size: 1.2em; +} + +.refresh-btn { + display: block; + margin: 0 auto 32px auto; + padding: 10px 28px; + background: var(--accent2); + color: #fff; + border: none; + border-radius: 7px; + cursor: pointer; + font-size: 1.1em; + font-weight: bold; + letter-spacing: 0.5px; + box-shadow: 0 1px 4px #15181d; + transition: background 0.2s; +} + +.refresh-btn:hover { + background: var(--accent); +} + +.join-btn { + display: block; + margin: 0 auto 0 auto; + position: absolute; + left: 50%; + bottom: 18px; + transform: translateX(-50%); + padding: 10px 24px; + background: var(--accent2); + color: #fff; + border-radius: 6px; + font-size: 1em; + font-weight: bold; + text-decoration: none; + transition: background 0.2s; + box-shadow: 0 1px 4px #15181d; +} + +.join-btn:hover { + background: var(--accent); +} + +@media (max-width: 900px) { + .container { + max-width: 98vw; + padding: 16px; + } + .rooms { + gap: 16px; + } +} + +@media (max-width: 600px) { + .rooms { + grid-template-columns: 1fr; + } + .room-card { + padding: 20px 10px; + } + .peer-label { + margin-bottom: 70px; + } +} diff --git a/public/css/landing.css b/public/css/landing.css index e31bf8bc..a788391d 100644 --- a/public/css/landing.css +++ b/public/css/landing.css @@ -2077,6 +2077,10 @@ main { # Common --------------------------------------------------------------*/ +.blue { + color: #00bfff; +} + .tac { text-align: center; } diff --git a/public/js/ActiveRooms.js b/public/js/ActiveRooms.js new file mode 100644 index 00000000..777651a0 --- /dev/null +++ b/public/js/ActiveRooms.js @@ -0,0 +1,89 @@ +'use strict'; + +console.log(window.location); + +const isTest = false; // Set to true for testing with mock data + +const roomsDiv = document.getElementById('rooms'); +const searchInput = document.getElementById('searchInput'); +const searchBtn = document.getElementById('search-btn'); +const refreshBtn = document.getElementById('refresh-btn'); + +let allRooms = []; + +searchBtn.addEventListener('click', handleSearch); +refreshBtn.addEventListener('click', fetchRooms); + +function setRoomsContent(html) { + roomsDiv.innerHTML = html; +} + +function getRoomsData(res) { + return !isTest ? res.data.activeRooms || [] : mockRooms(); +} + +function mockRooms(roomCount = 1000) { + return Array.from({ length: roomCount }, () => { + const id = getUUID(); + return { + id, + peers: Math.floor(Math.random() * 10) + 1, + join: `${window.location.origin}/${id}`, + }; + }); +} + +function getUUID() { + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) + ); +} + +async function fetchRooms() { + setRoomsContent('
Loading...
'); + try { + const res = await axios.get('/api/v1/activeRooms'); + if (res.status !== 200) throw new Error('Failed to fetch active rooms'); + allRooms = getRoomsData(res); + renderRooms(allRooms); + } catch (err) { + const errorMsg = err.response?.data?.error || err.message; + setRoomsContent(`
${errorMsg}
`); + } +} + +function renderRooms(rooms) { + if (!rooms.length) { + setRoomsContent('
No active rooms found.
'); + return; + } + setRoomsContent( + rooms + .map( + (room) => ` +
+
+ + ${room.id} +
+
+ + ${room.peers} +
+
${room.peers === 1 ? 'peer' : 'peers'}
+ + Join + +
+ ` + ) + .join('') + ); +} + +function handleSearch() { + const value = searchInput.value.trim().toLowerCase(); + renderRooms(!value ? allRooms : allRooms.filter((room) => room.id.toLowerCase().includes(value))); +} + +fetchRooms(); diff --git a/public/js/Brand.js b/public/js/Brand.js index 1d7f1b51..0fdaefa0 100644 --- a/public/js/Brand.js +++ b/public/js/Brand.js @@ -76,7 +76,7 @@ let BRAND = { }, about: { imageUrl: '../images/mirotalk-logo.gif', - title: 'WebRTC SFU v1.9.18', + title: 'WebRTC SFU v1.9.20', html: ` + + +
+ + + +