diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index e77fb133..390f2556 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 @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v2 # 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 back image" uses: docker/build-push-action@v1 @@ -66,7 +66,7 @@ jobs: uses: actions/checkout@v2 # 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 back image" uses: docker/build-push-action@v1 @@ -90,7 +90,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 @@ -137,7 +137,7 @@ jobs: check_for_duplicate_msg: true - name: Run Cypress tests - uses: cypress-io/github-action@v1 + uses: cypress-io/github-action@v2 if: ${{ env.GITHUB_REF_SLUG != 'master' }} env: CYPRESS_BASE_URL: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com @@ -148,7 +148,7 @@ jobs: working-directory: e2e - name: Run Cypress tests in prod - uses: cypress-io/github-action@v1 + uses: cypress-io/github-action@v2 if: ${{ env.GITHUB_REF_SLUG == 'master' }} env: CYPRESS_BASE_URL: https://play.workadventu.re 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/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 0ae6465f..a27d91df 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -22,7 +22,7 @@ import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Serv import {SocketManager, socketManager} from "../Services/SocketManager"; import {emitInBatch, resetPing} from "../Services/IoSocketHelpers"; import {clientEventsEmitter} from "../Services/ClientEventsEmitter"; -import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; +import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; export class IoSocketController { private nextUserId: number = 1; @@ -166,18 +166,21 @@ export class IoSocketController { if(room.isFull){ throw new Error('Room is full'); } - try { - const userData = await adminApi.fetchMemberDataByUuid(userUuid); - //console.log('USERDATA', userData) - memberTags = userData.tags; - memberTextures = userData.textures; - if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) { - throw new Error('No correct tags') + if (ADMIN_API_URL) { + try { + const userData = await adminApi.fetchMemberDataByUuid(userUuid); + //console.log('USERDATA', userData) + memberTags = userData.tags; + memberTextures = userData.textures; + if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) { + throw new Error('No correct tags') + } + //console.log('access granted for user '+userUuid+' and room '+roomId); + } catch (e) { + console.log('access not granted for user '+userUuid+' and room '+roomId); + console.error(e); + throw new Error('Client cannot acces this ressource.') } - //console.log('access granted for user '+userUuid+' and room '+roomId); - } catch (e) { - console.log('access not granted for user '+userUuid+' and room '+roomId); - throw new Error('Client cannot acces this ressource.') } // Generate characterLayers objects from characterLayers string[] @@ -238,22 +241,24 @@ export class IoSocketController { socketManager.handleJoinRoom(client); resetPing(client); - //get data information and shwo messages - adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => { - if (!res.messages) { - return; - } - res.messages.forEach((c: unknown) => { - const messageToSend = c as { type: string, message: string }; - socketManager.emitSendUserMessage({ - userUuid: client.userUuid, - type: messageToSend.type, - message: messageToSend.message - }) + //get data information and show messages + if (ADMIN_API_URL) { + adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => { + if (!res.messages) { + return; + } + res.messages.forEach((c: unknown) => { + const messageToSend = c as { type: string, message: string }; + socketManager.emitSendUserMessage({ + userUuid: client.userUuid, + type: messageToSend.type, + message: messageToSend.message + }) + }); + }).catch((err) => { + console.error('fetchMemberDataByUuid => err', err); }); - }).catch((err) => { - console.error('fetchMemberDataByUuid => err', err); - }); + } }, message: (ws, arrayBuffer, isBinary): void => { const client = ws as ExSocketInterface; 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/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 56c49284..55fd1bb7 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -3,7 +3,7 @@ const URL_ROOM_STARTED = "/Floor0/floor0.json"; const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64; const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false; -const ADMIN_API_URL = process.env.ADMIN_API_URL || 'http://admin'; +const ADMIN_API_URL = process.env.ADMIN_API_URL || ''; const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken'; const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; 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/JWTTokenManager.ts b/back/src/Services/JWTTokenManager.ts index f82fa001..8abb0e45 100644 --- a/back/src/Services/JWTTokenManager.ts +++ b/back/src/Services/JWTTokenManager.ts @@ -1,11 +1,11 @@ -import {ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable"; +import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable"; import {uuid} from "uuidv4"; import Jwt from "jsonwebtoken"; import {TokenInterface} from "../Controller/AuthenticateController"; import {adminApi, AdminApiData} from "../Services/AdminApi"; class JWTTokenManager { - + public createJWTToken(userUuid: string) { return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token } @@ -48,17 +48,21 @@ class JWTTokenManager { return; } - //verify user in admin - adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => { - resolve(tokenInterface.userUuid); - }).catch((err) => { - //anonymous user - if(err.response && err.response.status && err.response.status === 404){ + if (ADMIN_API_URL) { + //verify user in admin + adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => { resolve(tokenInterface.userUuid); - return; - } - reject(new Error('Authentication error, invalid token structure. ' + err)); - }); + }).catch((err) => { + //anonymous user + if(err.response && err.response.status && err.response.status === 404){ + resolve(tokenInterface.userUuid); + return; + } + reject(err); + }); + } else { + resolve(tokenInterface.userUuid); + } }); }); } @@ -66,7 +70,7 @@ class JWTTokenManager { private isValidToken(token: object): token is TokenInterface { return !(typeof((token as TokenInterface).userUuid) !== 'string'); } - + } -export const jwtTokenManager = new JWTTokenManager(); \ No newline at end of file +export const jwtTokenManager = new JWTTokenManager(); 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; } 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" diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 4edb4728..db86fbb4 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -3,6 +3,7 @@ local namespace = env.GITHUB_REF_SLUG, local tag = namespace, local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com", + local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://admin."+url else null, "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", "version": "1.0", "containers": { @@ -16,11 +17,12 @@ "env": { "SECRET_KEY": "tempSecretKeyNeedsToChange", "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN, - "ADMIN_API_URL": "https://admin."+url, "JITSI_ISS": env.JITSI_ISS, "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, - } + } + if adminUrl != null then { + "ADMIN_API_URL": adminUrl, + } else {} }, "front": { "image": "thecodingmachine/workadventure-front:"+tag, 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 5f5d37f2..edd3ddf0 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 46efc5e5..6ee49214 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); }); diff --git a/website/dist/choose-map.html b/website/dist/choose-map.html index a2366f1d..5fb1d8f7 100644 --- a/website/dist/choose-map.html +++ b/website/dist/choose-map.html @@ -36,6 +36,15 @@ +

CHOOSE YOUR MAP !

diff --git a/website/dist/create-map.html b/website/dist/create-map.html index d34cea77..0a398f45 100644 --- a/website/dist/create-map.html +++ b/website/dist/create-map.html @@ -36,6 +36,15 @@ +

CREATE YOUR MAP !

@@ -44,7 +53,7 @@
- < class="col"> +

Tools you will need

In order to build your own map for WorkAdventure, you need:

    diff --git a/website/dist/index.html b/website/dist/index.html index 08805709..1e06204a 100644 --- a/website/dist/index.html +++ b/website/dist/index.html @@ -33,12 +33,12 @@ - + - + @@ -46,7 +46,7 @@ - + @@ -97,17 +97,17 @@

Your workplace
but better

-

You are impatient to discover this new world? Click on "Play online" and meet new people or share this adventure with your colleagues and friends by clicking on "Private mode"

+

You are impatient to discover this new world? Click on "Work online" and meet new people or share this adventure with your colleagues and friends by clicking on "Work in private"

@@ -165,10 +165,10 @@

You can also create a private room with your friends or your team !

-

To try, press button private mode

+

To try, press button work in private

- START IN PRIVATE MODE + START WORKING IN PRIVATE

Don’t forget to activate your mic and camera, let’s play @@ -287,23 +287,23 @@

-

How to play

+

HOW IT WORKS

-

Choose your map

+

CHOOSE YOUR WORKSPACE

-

Select your character

+

SELECT YOUR WOKA

-

Let's go explore and talk !

+

LET'S GO TO YOUR OFFICE

@@ -313,13 +313,14 @@