From 3109adcfc9a635ed29c656159c2c528e75effdec Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Fri, 20 Dec 2024 21:10:50 +0100 Subject: [PATCH] [mirotalsfu] - add stats endpoint --- app/api/stats/stats.js | 33 +++++++++++++++++++++++ app/api/stats/stats.php | 29 ++++++++++++++++++++ app/api/stats/stats.py | 26 ++++++++++++++++++ app/api/stats/stats.sh | 10 +++++++ app/api/swagger.yaml | 34 ++++++++++++++++++++++++ app/src/Server.js | 44 ++++++++++++++++++++++++++++++- app/src/ServerApi.js | 6 +++++ app/src/config.template.js | 1 + package.json | 2 +- public/js/Room.js | 4 +-- public/js/RoomClient.js | 2 +- tests/test-ServerAPI.js | 54 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 app/api/stats/stats.js create mode 100644 app/api/stats/stats.php create mode 100644 app/api/stats/stats.py create mode 100755 app/api/stats/stats.sh diff --git a/app/api/stats/stats.js b/app/api/stats/stats.js new file mode 100644 index 00000000..827c2a22 --- /dev/null +++ b/app/api/stats/stats.js @@ -0,0 +1,33 @@ +'use strict'; + +async function getStats() { + try { + // Use dynamic import with await + const { default: fetch } = await import('node-fetch'); + + const API_KEY_SECRET = 'mirotalksfu_default_secret'; + const MIROTALK_URL = 'https://sfu.mirotalk.com/api/v1/stats'; + //const MIROTALK_URL = 'http://localhost:3010/api/v1/stats'; + + const response = await fetch(MIROTALK_URL, { + method: 'GET', + headers: { + authorization: API_KEY_SECRET, + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); + if (data.error) { + console.log('Error:', data.error); + } else { + if (data) { + const formattedData = JSON.stringify(data, null, 2); + console.log(formattedData); + } + } + } catch (error) { + console.error('Error fetching data:', error); + } +} + +getStats(); diff --git a/app/api/stats/stats.php b/app/api/stats/stats.php new file mode 100644 index 00000000..ec687a78 --- /dev/null +++ b/app/api/stats/stats.php @@ -0,0 +1,29 @@ + { + try { + // Check if endpoint allowed + if (restApi.allowed && !restApi.allowed.stats) { + return res.status(403).json({ + success: false, + 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 } = req.headers; + const api = new ServerApi(host, authorization); + + if (!api.isAuthorized()) { + log.debug('MiroTalk get meetings - Unauthorized', { + header: req.headers, + body: req.body, + }); + return res.status(403).json({ error: 'Unauthorized!' }); + } + + const { totalRooms, totalUsers } = api.getStats(roomList); + + res.json({ + success: true, + totalUsers, + totalRooms, + }); + + // log.debug the output if all done + log.debug('MiroTalk get stats - Authorized', { + header: req.headers, + body: req.body, + totalUsers, + totalRooms, + }); + } catch (error) { + console.error('Error fetching stats', error); + res.status(500).json({ success: false, error: 'Failed to retrieve stats.' }); + } + }); + // request meetings list app.get([restApi.basePath + '/meetings'], (req, res) => { // Check if endpoint allowed diff --git a/app/src/ServerApi.js b/app/src/ServerApi.js index 741732fc..d73128cf 100644 --- a/app/src/ServerApi.js +++ b/app/src/ServerApi.js @@ -21,6 +21,12 @@ module.exports = class ServerApi { return true; } + getStats(roomList) { + const totalUsers = Array.from(roomList.values()).reduce((total, room) => total + room.peers.size, 0); + const totalRooms = roomList.size; + return { totalUsers, totalRooms }; + } + 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 f9154826..35ee0a7c 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -141,6 +141,7 @@ module.exports = { keySecret: 'mirotalksfu_default_secret', // Define which endpoints are allowed allowed: { + stats: true, meetings: false, meeting: true, join: true, diff --git a/package.json b/package.json index 1db5f070..49ced38d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.6.53", + "version": "1.6.54", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { diff --git a/public/js/Room.js b/public/js/Room.js index 55bbcbc2..0c882477 100644 --- a/public/js/Room.js +++ b/public/js/Room.js @@ -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.6.53 + * @version 1.6.54 * */ @@ -4619,7 +4619,7 @@ function showAbout() { imageUrl: image.about, customClass: { image: 'img-about' }, position: 'center', - title: 'WebRTC SFU v1.6.53', + title: 'WebRTC SFU v1.6.54', html: `
diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index 99828476..e7c79f08 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -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.6.53 + * @version 1.6.54 * */ diff --git a/tests/test-ServerAPI.js b/tests/test-ServerAPI.js index 3f9f7d90..331d0f4a 100644 --- a/tests/test-ServerAPI.js +++ b/tests/test-ServerAPI.js @@ -38,6 +38,60 @@ describe('test-ServerAPI', () => { }); }); + describe('getStats', () => { + it('should return total number of rooms and users', () => { + const roomList = new Map([ + [ + 'room1', + { + peers: new Map([ + ['peer1', { peer_info: { peer_name: 'John Doe' } }], + ['peer2', { peer_info: { peer_name: 'Jane Doe' } }], + ]), + }, + ], + [ + 'room2', + { + peers: new Map([['peer3', { peer_info: { peer_name: 'Sam Smith' } }]]), + }, + ], + ]); + + const result = serverApi.getStats(roomList); + + result.should.deepEqual({ + totalUsers: 3, + totalRooms: 2, + }); + }); + + it('should return 0 users when there are no peers in any room', () => { + const roomList = new Map([ + ['room1', { peers: new Map() }], + ['room2', { peers: new Map() }], + ]); + + const result = serverApi.getStats(roomList); + + result.should.deepEqual({ + totalUsers: 0, + totalRooms: 2, + }); + }); + + it('should return 0 rooms when roomList is empty', () => { + const roomList = new Map(); + + const result = serverApi.getStats(roomList); + + result.should.deepEqual({ + totalUsers: 0, + totalRooms: 0, + }); + }); + }); + describe('getMeetings', () => { it('should return formatted meetings with peer information', () => { const roomList = new Map([