From 65a710d1f4f0bae88f3d7c28514a4344b0499208 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 26 Oct 2020 15:07:34 +0100 Subject: [PATCH 1/5] disabling audio/video in jitsi carry over to wa --- front/src/WebRtc/JitsiFactory.ts | 25 +++++++++++++++++++++++++ front/src/WebRtc/MediaManager.ts | 8 ++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index d0658218..191642fb 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -27,6 +27,9 @@ const interfaceConfig = { class JitsiFactory { private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any + private audioCallback = this.onAudioChange.bind(this); + private videoCallback = this.onVideoChange.bind(this); + public start(roomName: string, playerName:string, jwt?: string): void { CoWebsiteManager.insertCoWebsite((cowebsiteDiv => { const domain = JITSI_URL; @@ -48,13 +51,35 @@ class JitsiFactory { } this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); this.jitsiApi.executeCommand('displayName', playerName); + + this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback); + this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback); })); } public stop(): void { + this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback); + this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback); this.jitsiApi?.dispose(); CoWebsiteManager.closeCoWebsite(); } + + private onAudioChange({muted}: {muted: boolean}): void { + if (muted && mediaManager.constraintsMedia.audio === true) { + mediaManager.disableMicrophone(); + } else if(!muted && mediaManager.constraintsMedia.audio === false) { + mediaManager.enableMicrophone(); + } + } + + private onVideoChange({muted}: {muted: boolean}): void { + if (muted && mediaManager.constraintsMedia.video !== false) { + mediaManager.disableCamera(); + } else if(!muted && mediaManager.constraintsMedia.video === false) { + mediaManager.enableCamera(); + } + } + } export const jitsiFactory = new JitsiFactory(); \ No newline at end of file diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 557f12fb..ab6d9a3b 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -135,7 +135,7 @@ export class MediaManager { gameOverlay.classList.remove('active'); } - private enableCamera() { + public enableCamera() { this.cinemaClose.style.display = "none"; this.cinemaBtn.classList.remove("disabled"); this.cinema.style.display = "block"; @@ -145,7 +145,7 @@ export class MediaManager { }); } - private async disableCamera() { + public async disableCamera() { this.cinemaClose.style.display = "block"; this.cinema.style.display = "none"; this.cinemaBtn.classList.add("disabled"); @@ -161,7 +161,7 @@ export class MediaManager { } } - private enableMicrophone() { + public enableMicrophone() { this.microphoneClose.style.display = "none"; this.microphone.style.display = "block"; this.microphoneBtn.classList.remove("disabled"); @@ -172,7 +172,7 @@ export class MediaManager { }); } - private async disableMicrophone() { + public async disableMicrophone() { this.microphoneClose.style.display = "block"; this.microphone.style.display = "none"; this.microphoneBtn.classList.add("disabled"); From 11127db9f375d21c3b392d48308bf8c99708a91e Mon Sep 17 00:00:00 2001 From: Romain Lespinasse Date: Mon, 26 Oct 2020 20:10:59 +0100 Subject: [PATCH 2/5] ci: update to github-slug-action v1.1.1 --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index e77fb133..790dbaaa 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -20,7 +20,7 @@ jobs: # Create a slugified value of the branch - - uses: rlespinasse/github-slug-action@master + - uses: rlespinasse/github-slug-action@1.1.1 - name: "Build and push front image" uses: docker/build-push-action@v1 From 67aa2f4b6c42c1784624d6dc510905c7ac07e97c Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 27 Oct 2020 16:59:12 +0100 Subject: [PATCH 3/5] opening and closing jitsi windows now trigger some transitions --- front/dist/index.html | 22 +----- front/dist/resources/style/style.css | 56 +++++++++------ front/src/Phaser/Game/GameScene.ts | 13 +--- front/src/WebRtc/CoWebsiteManager.ts | 101 +++++++++++++++++++-------- front/src/WebRtc/JitsiFactory.ts | 22 +++--- front/src/index.ts | 15 ++-- 6 files changed, 133 insertions(+), 96 deletions(-) diff --git a/front/dist/index.html b/front/dist/index.html index 8e957965..0e696622 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -42,30 +42,14 @@
-
-
+
+
-
- -
@@ -88,7 +72,7 @@
-
+
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 23a4bba6..1acf6d2a 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -242,15 +242,10 @@ body { .main-container { height: 100vh; width: 100vw; - display: flex; - align-items: stretch; + position: absolute; } @media (min-aspect-ratio: 1/1) { - .main-container { - flex-direction: row; - } - .game-overlay { flex-direction: row; } @@ -266,12 +261,21 @@ body { .sidebar > div:hover { max-height: 25%; } + + #cowebsite { + right: 0; + top: 0; + width: 50%; + height: 100vh; + } + #cowebsite.loading { + transform: translateX(90%); + } + #cowebsite.hidden { + transform: translateX(100%); + } } @media (max-aspect-ratio: 1/1) { - .main-container { - flex-direction: column; - } - .game-overlay { flex-direction: column; } @@ -288,24 +292,36 @@ body { .sidebar > div:hover { max-width: 25%; } + + #cowebsite { + left: 0; + bottom: 0; + width: 100%; + height: 50%; + } + #cowebsite.loading { + transform: translateY(90%); + } + #cowebsite.hidden { + transform: translateY(100%); + } } -.game { - flex-basis: 100%; +#game { + width: 100%; position: relative; /* Position relative is needed for the game-overlay. */ } /* A potentially shared website could appear in an iframe in the cowebsite space. */ -.cowebsite { - flex-basis: 100%; - transition: flex-basis 0.5s; +#cowebsite { + position: fixed; + transition: transform 0.5s; +} +#cowebsite.loading { + background-color: gray; } -/*.cowebsite:hover { - flex-basis: 100%; -}*/ - -.cowebsite > iframe { +#cowebsite > iframe { width: 100%; height: 100%; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index bfc6402e..96648255 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -38,7 +38,7 @@ import CanvasTexture = Phaser.Textures.CanvasTexture; import GameObject = Phaser.GameObjects.GameObject; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import {GameMap} from "./GameMap"; -import {CoWebsiteManager} from "../../WebRtc/CoWebsiteManager"; +import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import {mediaManager} from "../../WebRtc/MediaManager"; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; import {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; @@ -292,13 +292,6 @@ export class GameScene extends ResizableScene implements CenterListener { // }); // }); } - - // TEST: let's load a module dynamically! - /*let foo = "http://maps.workadventure.localhost/computer.js"; - import(/* webpackIgnore: true * / foo).then(result => { - console.log(result); - - });*/ } //hook initialisation @@ -476,9 +469,9 @@ export class GameScene extends ResizableScene implements CenterListener { this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => { if (newValue === undefined) { - CoWebsiteManager.closeCoWebsite(); + coWebsiteManager.closeCoWebsite(); } else { - CoWebsiteManager.loadCoWebsite(newValue as string); + coWebsiteManager.loadCoWebsite(newValue as string); } }); this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => { diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 1793335b..ab63e60a 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -2,47 +2,90 @@ import {HtmlUtils} from "./HtmlUtils"; export type CoWebsiteStateChangedCallback = () => void; -export class CoWebsiteManager { +enum iframeStates { + closed = 1, + loading, // loading an iframe can be slow, so we show some placeholder until it is ready + opened, +} - private static observers = new Array(); +const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe. +const animationTime = 500; //time used by the css transitions, in ms. - public static loadCoWebsite(url: string): void { - const cowebsiteDiv = HtmlUtils.getElementByIdOrFail("cowebsite"); +class CoWebsiteManager { + + private opened: iframeStates = iframeStates.closed; + + private observers = new Array(); + + private close(): HTMLDivElement { + const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); + cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition + cowebsiteDiv.classList.add('hidden'); + this.opened = iframeStates.closed; + return cowebsiteDiv; + } + private load(): HTMLDivElement { + const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); + cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition + cowebsiteDiv.classList.add('loading'); + this.opened = iframeStates.loading; + return cowebsiteDiv; + } + private open(): HTMLDivElement { + const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); + cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition + this.opened = iframeStates.opened; + return cowebsiteDiv; + } + + public loadCoWebsite(url: string): void { + const cowebsiteDiv = this.load(); cowebsiteDiv.innerHTML = ''; const iframe = document.createElement('iframe'); iframe.id = 'cowebsite-iframe'; iframe.src = url; + const onloadPromise = new Promise((resolve) => { + iframe.onload = () => resolve(); + }); cowebsiteDiv.appendChild(iframe); - //iframe.onload = () => { - // onload can be long to trigger. Maybe we should display the website, whatever happens, after 1 second? - CoWebsiteManager.fire(); - //} + const onTimeoutPromise = new Promise((resolve) => { + setTimeout(() => resolve(), 2000); + }); + Promise.race([onloadPromise, onTimeoutPromise]).then(() => { + this.open(); + setTimeout(() => { + this.fire(); + }, animationTime) + }); } /** * Just like loadCoWebsite but the div can be filled by the user. */ - public static insertCoWebsite(callback: (cowebsite: HTMLDivElement) => void): void { - const cowebsiteDiv = HtmlUtils.getElementByIdOrFail("cowebsite"); - cowebsiteDiv.innerHTML = ''; - - callback(cowebsiteDiv); - //iframe.onload = () => { - // onload can be long to trigger. Maybe we should display the website, whatever happens, after 1 second? - CoWebsiteManager.fire(); - //} + public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise): void { + const cowebsiteDiv = this.load(); + callback(cowebsiteDiv).then(() => { + this.open(); + setTimeout(() => { + this.fire(); + }, animationTime) + }); } - public static closeCoWebsite(): void { - const cowebsiteDiv = HtmlUtils.getElementByIdOrFail("cowebsite"); - cowebsiteDiv.innerHTML = ''; - CoWebsiteManager.fire(); + public closeCoWebsite(): Promise { + return new Promise((resolve, reject) => { + const cowebsiteDiv = this.close(); + this.fire(); + setTimeout(() => { + resolve(); + setTimeout(() => cowebsiteDiv.innerHTML = '', 500) + }, animationTime) + }); } - public static getGameSize(): {width: number, height: number} { - const hasChildren = HtmlUtils.getElementByIdOrFail("cowebsite").children.length > 0; - if (hasChildren === false) { + public getGameSize(): {width: number, height: number} { + if (this.opened !== iframeStates.opened) { return { width: window.innerWidth, height: window.innerHeight @@ -61,13 +104,15 @@ export class CoWebsiteManager { } } - public static onStateChange(observer: CoWebsiteStateChangedCallback) { - CoWebsiteManager.observers.push(observer); + public onStateChange(observer: CoWebsiteStateChangedCallback) { + this.observers.push(observer); } - private static fire(): void { - for (const callback of CoWebsiteManager.observers) { + private fire(): void { + for (const callback of this.observers) { callback(); } } } + +export const coWebsiteManager = new CoWebsiteManager(); \ No newline at end of file diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 191642fb..45b9b3cf 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -1,6 +1,6 @@ -import {CoWebsiteManager} from "./CoWebsiteManager"; import {JITSI_URL} from "../Enum/EnvironmentVariable"; import {mediaManager} from "./MediaManager"; +import {coWebsiteManager} from "./CoWebsiteManager"; declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any const interfaceConfig = { @@ -31,9 +31,9 @@ class JitsiFactory { private videoCallback = this.onVideoChange.bind(this); public start(roomName: string, playerName:string, jwt?: string): void { - CoWebsiteManager.insertCoWebsite((cowebsiteDiv => { + coWebsiteManager.insertCoWebsite((cowebsiteDiv => { const domain = JITSI_URL; - const options = { + const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any roomName: roomName, jwt: jwt, width: "100%", @@ -49,19 +49,23 @@ class JitsiFactory { if (!options.jwt) { delete options.jwt; } - this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); - this.jitsiApi.executeCommand('displayName', playerName); + + return new Promise((resolve) => { + options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations. + this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); + this.jitsiApi.executeCommand('displayName', playerName); - this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback); - this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback); + this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback); + this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback); + }); })); } - public stop(): void { + public async stop(): Promise { + await coWebsiteManager.closeCoWebsite(); this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback); this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback); this.jitsiApi?.dispose(); - CoWebsiteManager.closeCoWebsite(); } private onAudioChange({muted}: {muted: boolean}): void { diff --git a/front/src/index.ts b/front/src/index.ts index e12d8707..fe7ceb34 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -10,12 +10,9 @@ import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene"; import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer; import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; -import {CoWebsiteManager} from "./WebRtc/CoWebsiteManager"; -import {gameManager} from "./Phaser/Game/GameManager"; import {ResizableScene} from "./Phaser/Login/ResizableScene"; import {EntryScene} from "./Phaser/Login/EntryScene"; - -//CoWebsiteManager.loadCoWebsite('https://thecodingmachine.com'); +import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; // Load Jitsi if the environment variable is set. if (JITSI_URL) { @@ -24,7 +21,7 @@ if (JITSI_URL) { document.head.appendChild(jitsiScript); } -const {width, height} = CoWebsiteManager.getGameSize(); +const {width, height} = coWebsiteManager.getGameSize(); const config: GameConfig = { title: "WorkAdventure", @@ -53,8 +50,7 @@ cypressAsserter.gameStarted(); const game = new Phaser.Game(config); window.addEventListener('resize', function (event) { - const {width, height} = CoWebsiteManager.getGameSize(); - + const {width, height} = coWebsiteManager.getGameSize(); game.scale.resize(width / RESOLUTION, height / RESOLUTION); // Let's trigger the onResize method of any active scene that is a ResizableScene @@ -64,8 +60,7 @@ window.addEventListener('resize', function (event) { } } }); -CoWebsiteManager.onStateChange(() => { - const {width, height} = CoWebsiteManager.getGameSize(); - +coWebsiteManager.onStateChange(() => { + const {width, height} = coWebsiteManager.getGameSize(); game.scale.resize(width / RESOLUTION, height / RESOLUTION); }); From d472f984b5388166121a4fbf128ce11c0def5a57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2020 20:55:36 +0000 Subject: [PATCH 4/5] Bump systeminformation from 4.27.5 to 4.27.11 in /back Bumps [systeminformation](https://github.com/sebhildebrandt/systeminformation) from 4.27.5 to 4.27.11. - [Release notes](https://github.com/sebhildebrandt/systeminformation/releases) - [Changelog](https://github.com/sebhildebrandt/systeminformation/blob/master/CHANGELOG.md) - [Commits](https://github.com/sebhildebrandt/systeminformation/commits) Signed-off-by: dependabot[bot] --- back/package.json | 2 +- back/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/back/package.json b/back/package.json index d989fb1e..bb34e186 100644 --- a/back/package.json +++ b/back/package.json @@ -49,7 +49,7 @@ "multer": "^1.4.2", "prom-client": "^12.0.0", "query-string": "^6.13.3", - "systeminformation": "^4.26.5", + "systeminformation": "^4.27.11", "ts-node-dev": "^1.0.0-pre.44", "typescript": "^3.8.3", "uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0", diff --git a/back/yarn.lock b/back/yarn.lock index fea8516e..1bd7c802 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -2345,10 +2345,10 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -systeminformation@^4.26.5: - version "4.27.5" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.27.5.tgz#af304fbfd0e7ba51c87512333691b58b4ad90e43" - integrity sha512-EysogxKqREk54ZYDEFcsCODv8GymKZcyiSfegYit8dKhPjzuQr+KX4GFHjssWjYrWFEIM2bYNsFrZX5eufeAXg== +systeminformation@^4.27.11: + version "4.27.11" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.27.11.tgz#6dbe96e48091444f80dab6c05ee1901286826b60" + integrity sha512-U7bigXbOnsB8k1vNHS0Y13RCsRz5/UohiUmND+3mMUL6vfzrpbe/h4ZqewowB+B+tJNnmGFDj08Z8xGfYo45dQ== table@^5.2.3: version "5.4.6" From b1d2543631f3136af60c24b65f60afbc33692449 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Fri, 30 Oct 2020 15:23:50 +0100 Subject: [PATCH 5/5] improvment: added prometheus metrics for the number of groups in a room --- back/src/Controller/PrometheusController.ts | 1 - back/src/Model/GameRoom.ts | 4 +- back/src/Model/Group.ts | 16 +++++- back/src/Services/GaugeManager.ts | 54 +++++++++++++++++++++ back/src/Services/SocketManager.ts | 37 ++++---------- 5 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 back/src/Services/GaugeManager.ts diff --git a/back/src/Controller/PrometheusController.ts b/back/src/Controller/PrometheusController.ts index 05570466..e854cf43 100644 --- a/back/src/Controller/PrometheusController.ts +++ b/back/src/Controller/PrometheusController.ts @@ -1,5 +1,4 @@ import {App} from "../Server/sifrr.server"; -import {IoSocketController} from "_Controller/IoSocketController"; import {HttpRequest, HttpResponse} from "uWebSockets.js"; const register = require('prom-client').register; const collectDefaultMetrics = require('prom-client').collectDefaultMetrics; diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 5b42f418..eaad701a 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -152,7 +152,7 @@ export class GameRoom { closestItem.join(user); } else { const closestUser : User = closestItem; - const group: Group = new Group([ + const group: Group = new Group(this.roomId,[ user, closestUser ], this.connectCallback, this.disconnectCallback, this.positionNotifier); @@ -200,7 +200,6 @@ export class GameRoom { if (group === undefined) { throw new Error("The user is part of no group"); } - const oldPosition = group.getPosition(); group.leave(user); if (group.isEmpty()) { this.positionNotifier.leave(group); @@ -209,6 +208,7 @@ export class GameRoom { throw new Error("Could not find group "+group.getId()+" referenced by user "+user.id+" in World."); } this.groups.delete(group); + //todo: is the group garbage collected? } else { group.updatePosition(); //this.positionNotifier.updatePosition(group, group.getPosition(), oldPosition); diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index d3b042a6..f26a0e0d 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -3,6 +3,7 @@ import { User } from "./User"; import {PositionInterface} from "_Model/PositionInterface"; import {Movable} from "_Model/Movable"; import {PositionNotifier} from "_Model/PositionNotifier"; +import {gaugeManager} from "../Services/GaugeManager"; export class Group implements Movable { static readonly MAX_PER_GROUP = 4; @@ -13,12 +14,23 @@ export class Group implements Movable { private users: Set; private x!: number; private y!: number; + private hasEditedGauge: boolean = false; + private wasDestroyed: boolean = false; + private roomId: string; - constructor(users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) { + constructor(roomId: string, users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) { + this.roomId = roomId; this.users = new Set(); this.id = Group.nextId; Group.nextId++; + //we only send a event for prometheus metrics if the group lives more than 5 seconds + setTimeout(() => { + if (!this.wasDestroyed) { + this.hasEditedGauge = true; + gaugeManager.incNbGroupsPerRoomGauge(roomId); + } + }, 5000); users.forEach((user: User) => { this.join(user); @@ -113,9 +125,11 @@ export class Group implements Movable { */ destroy(): void { + if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId); for (const user of this.users) { this.leave(user); } + this.wasDestroyed = true; } get getSize(){ diff --git a/back/src/Services/GaugeManager.ts b/back/src/Services/GaugeManager.ts new file mode 100644 index 00000000..f8af822b --- /dev/null +++ b/back/src/Services/GaugeManager.ts @@ -0,0 +1,54 @@ +import {Counter, Gauge} from "prom-client"; + +//this class should manage all the custom metrics used by prometheus +class GaugeManager { + private nbClientsGauge: Gauge; + private nbClientsPerRoomGauge: Gauge; + private nbGroupsPerRoomGauge: Gauge; + private nbGroupsPerRoomCounter: Counter; + + constructor() { + this.nbClientsGauge = new Gauge({ + name: 'workadventure_nb_sockets', + help: 'Number of connected sockets', + labelNames: [ ] + }); + this.nbClientsPerRoomGauge = new Gauge({ + name: 'workadventure_nb_clients_per_room', + help: 'Number of clients per room', + labelNames: [ 'room' ] + }); + + this.nbGroupsPerRoomCounter = new Counter({ + name: 'workadventure_counter_groups_per_room', + help: 'Counter of groups per room', + labelNames: [ 'room' ] + }); + this.nbGroupsPerRoomGauge = new Gauge({ + name: 'workadventure_nb_groups_per_room', + help: 'Number of groups per room', + labelNames: [ 'room' ] + }); + } + + incNbClientPerRoomGauge(roomId: string): void { + this.nbClientsGauge.inc(); + this.nbClientsPerRoomGauge.inc({ room: roomId }); + } + + decNbClientPerRoomGauge(roomId: string): void { + this.nbClientsGauge.dec(); + this.nbClientsPerRoomGauge.dec({ room: roomId }); + } + + incNbGroupsPerRoomGauge(roomId: string): void { + this.nbGroupsPerRoomCounter.inc({ room: roomId }) + this.nbGroupsPerRoomGauge.inc({ room: roomId }) + } + + decNbGroupsPerRoomGauge(roomId: string): void { + this.nbGroupsPerRoomGauge.dec({ room: roomId }) + } +} + +export const gaugeManager = new GaugeManager(); \ No newline at end of file diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 35f97c37..4bd26778 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -23,7 +23,6 @@ import { WebRtcStartMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage, - CharacterLayerMessage, SendUserMessage } from "../Messages/generated/messages_pb"; import {PointInterface} from "../Model/Websocket/PointInterface"; @@ -37,11 +36,11 @@ import {Movable} from "../Model/Movable"; import {PositionInterface} from "../Model/PositionInterface"; import {adminApi, CharacterTexture} from "./AdminApi"; import Direction = PositionMessage.Direction; -import {Gauge} from "prom-client"; import {emitError, emitInBatch} from "./IoSocketHelpers"; import Jwt from "jsonwebtoken"; import {JITSI_URL} from "../Enum/EnvironmentVariable"; import {clientEventsEmitter} from "./ClientEventsEmitter"; +import {gaugeManager} from "./GaugeManager"; interface AdminSocketRoomsList { [index: string]: number; @@ -58,30 +57,13 @@ export interface AdminSocketData { export class SocketManager { private Worlds: Map = new Map(); private sockets: Map = new Map(); - private nbClientsGauge: Gauge; - private nbClientsPerRoomGauge: Gauge; - + constructor() { - this.nbClientsGauge = new Gauge({ - name: 'workadventure_nb_sockets', - help: 'Number of connected sockets', - labelNames: [ ] + clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => { + gaugeManager.incNbClientPerRoomGauge(roomId); }); - this.nbClientsPerRoomGauge = new Gauge({ - name: 'workadventure_nb_clients_per_room', - help: 'Number of clients per room', - labelNames: [ 'room' ] - }); - - clientEventsEmitter.registerToClientJoin((clientUUid, roomId) => { - this.nbClientsGauge.inc(); - // Let's log server load when a user joins - console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)'); - }); - clientEventsEmitter.registerToClientLeave((clientUUid, roomId) => { - this.nbClientsGauge.dec(); - // Let's log server load when a user leaves - console.log('A user left (', this.sockets.size, ' connected users)'); + clientEventsEmitter.registerToClientLeave((clientUUid: string, roomId: string) => { + gaugeManager.decNbClientPerRoomGauge(roomId); }); } @@ -107,7 +89,6 @@ export class SocketManager { const viewport = client.viewport; try { this.sockets.set(client.userId, client); //todo: should this be at the end of the function? - clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId); //join new previous room const gameRoom = this.joinRoom(client, position); @@ -377,8 +358,8 @@ export class SocketManager { } finally { //delete Client.roomId; this.sockets.delete(Client.userId); - this.nbClientsPerRoomGauge.dec({ room: Client.roomId }); clientEventsEmitter.emitClientLeave(Client.userUuid, Client.roomId); + console.log('A user left (', this.sockets.size, ' connected users)'); } } } @@ -410,8 +391,6 @@ export class SocketManager { private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom { const roomId = client.roomId; - //join user in room - this.nbClientsPerRoomGauge.inc({ room: roomId }); client.position = position; const world = this.Worlds.get(roomId) @@ -425,6 +404,8 @@ export class SocketManager { }); //join world world.join(client, client.position); + clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId); + console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)'); return world; }