From 6f1fc08b8247052a0e0fa705b6997a586190b57a Mon Sep 17 00:00:00 2001 From: Mewp Date: Mon, 11 Jan 2021 03:03:23 +0100 Subject: [PATCH 01/10] Add a docker-compose file for hosting under a single domain. --- docker-compose.single-domain.yaml | 179 ++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 docker-compose.single-domain.yaml diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml new file mode 100644 index 00000000..3ffecb60 --- /dev/null +++ b/docker-compose.single-domain.yaml @@ -0,0 +1,179 @@ +version: "3" +services: + reverse-proxy: + image: traefik:v2.0 + command: + - --api.insecure=true + - --providers.docker + - --entryPoints.web.address=:80 + - --entryPoints.websecure.address=:443 + ports: + - "80:80" + - "443:443" + # The Web UI (enabled by --api.insecure=true) + - "8080:8080" + depends_on: + - back + - front + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + front: + image: thecodingmachine/nodejs:14 + environment: + DEBUG_MODE: "$DEBUG_MODE" + JITSI_URL: $JITSI_URL + JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" + HOST: "0.0.0.0" + NODE_ENV: development + API_URL: /pusher + UPLOADER_URL: /uploader + ADMIN_URL: /admin + MAPS_URL: /maps + STARTUP_COMMAND_1: yarn install + TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443" + TURN_USER: workadventure + TURN_PASSWORD: WorkAdventure123 + command: yarn run start + volumes: + - ./front:/usr/src/app + labels: + - "traefik.http.routers.front.rule=PathPrefix(`/`)" + - "traefik.http.routers.front.entryPoints=web,traefik" + - "traefik.http.services.front.loadbalancer.server.port=8080" + - "traefik.http.routers.front-ssl.rule=PathPrefix(`/`)" + - "traefik.http.routers.front-ssl.entryPoints=websecure" + - "traefik.http.routers.front-ssl.tls=true" + - "traefik.http.routers.front-ssl.service=front" + + pusher: + image: thecodingmachine/nodejs:12 + command: yarn dev + #command: yarn run prod + #command: yarn run profile + environment: + DEBUG: "*" + STARTUP_COMMAND_1: yarn install + SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" + SECRET_KEY: yourSecretKey + ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" + API_URL: back:50051 + JITSI_URL: $JITSI_URL + JITSI_ISS: $JITSI_ISS + volumes: + - ./pusher:/usr/src/app + labels: + - "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher" + - "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)" + - "traefik.http.routers.pusher.middlewares=strip-pusher-prefix@docker" + - "traefik.http.routers.pusher.entryPoints=web" + - "traefik.http.services.pusher.loadbalancer.server.port=8080" + - "traefik.http.routers.pusher-ssl.rule=PathPrefix(`/pusher`)" + - "traefik.http.routers.pusher-ssl.middlewares=strip-pusher-prefix@docker" + - "traefik.http.routers.pusher-ssl.entryPoints=websecure" + - "traefik.http.routers.pusher-ssl.tls=true" + - "traefik.http.routers.pusher-ssl.service=pusher" + + maps: + image: thecodingmachine/nodejs:12-apache + environment: + DEBUG_MODE: "$DEBUG_MODE" + HOST: "0.0.0.0" + NODE_ENV: development + #APACHE_DOCUMENT_ROOT: dist/ + #APACHE_EXTENSIONS: headers + #APACHE_EXTENSION_HEADERS: 1 + STARTUP_COMMAND_0: sudo a2enmod headers + STARTUP_COMMAND_1: yarn install + STARTUP_COMMAND_2: yarn run dev & + volumes: + - ./maps:/var/www/html + labels: + - "traefik.http.middlewares.strip-maps-prefix.stripprefix.prefixes=/maps" + - "traefik.http.routers.maps.rule=PathPrefix(`/maps`)" + - "traefik.http.routers.maps.middlewares=strip-maps-prefix@docker" + - "traefik.http.routers.maps.entryPoints=web,traefik" + - "traefik.http.services.maps.loadbalancer.server.port=80" + - "traefik.http.routers.maps-ssl.rule=PathPrefix(`/maps`)" + - "traefik.http.routers.maps-ssl.middlewares=strip-maps-prefix@docker" + - "traefik.http.routers.maps-ssl.entryPoints=websecure" + - "traefik.http.routers.maps-ssl.tls=true" + - "traefik.http.routers.maps-ssl.service=maps" + + back: + image: thecodingmachine/nodejs:12 + command: yarn dev + #command: yarn run profile + environment: + DEBUG: "*" + STARTUP_COMMAND_1: yarn install + SECRET_KEY: yourSecretKey + SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" + ALLOW_ARTILLERY: "true" + ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" + JITSI_URL: $JITSI_URL + JITSI_ISS: $JITSI_ISS + volumes: + - ./back:/usr/src/app + labels: + - "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api" + - "traefik.http.routers.back.rule=PathPrefix(`/api`)" + - "traefik.http.routers.back.middlewares=strip-api-prefix@docker" + - "traefik.http.routers.back.entryPoints=web" + - "traefik.http.services.back.loadbalancer.server.port=8080" + - "traefik.http.routers.back-ssl.rule=PathPrefix(`/api`)" + - "traefik.http.routers.back-ssl.middlewares=strip-api-prefix@docker" + - "traefik.http.routers.back-ssl.entryPoints=websecure" + - "traefik.http.routers.back-ssl.tls=true" + - "traefik.http.routers.back-ssl.service=back" + + uploader: + image: thecodingmachine/nodejs:12 + command: yarn dev + #command: yarn run profile + environment: + DEBUG: "*" + STARTUP_COMMAND_1: yarn install + volumes: + - ./uploader:/usr/src/app + labels: + - "traefik.http.middlewares.strip-uploader-prefix.stripprefix.prefixes=/uploader" + - "traefik.http.routers.uploader.rule=PathPrefix(`/uploader`)" + - "traefik.http.routers.uploader.middlewares=strip-uploader-prefix@docker" + - "traefik.http.routers.uploader.entryPoints=web" + - "traefik.http.services.uploader.loadbalancer.server.port=8080" + - "traefik.http.routers.uploader-ssl.rule=PathPrefix(`/uploader`)" + - "traefik.http.routers.uploader-ssl.middlewares=strip-uploader-prefix@docker" + - "traefik.http.routers.uploader-ssl.entryPoints=websecure" + - "traefik.http.routers.uploader-ssl.tls=true" + - "traefik.http.routers.uploader-ssl.service=uploader" + + website: + image: thecodingmachine/nodejs:12-apache + environment: + STARTUP_COMMAND_1: npm install + STARTUP_COMMAND_2: npm run watch & + APACHE_DOCUMENT_ROOT: dist/ + volumes: + - ./website:/var/www/html + labels: + - "traefik.http.routers.website.rule=Host(`workadventure.localhost`)" + - "traefik.http.routers.website.entryPoints=web" + - "traefik.http.services.website.loadbalancer.server.port=80" + - "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)" + - "traefik.http.routers.website-ssl.entryPoints=websecure" + - "traefik.http.routers.website-ssl.tls=true" + - "traefik.http.routers.website-ssl.service=website" + + messages: + #image: thecodingmachine/nodejs:14 + image: thecodingmachine/workadventure-back-base:latest + environment: + #STARTUP_COMMAND_0: sudo apt-get install -y inotify-tools + STARTUP_COMMAND_1: yarn install + STARTUP_COMMAND_2: yarn run proto:watch + volumes: + - ./messages:/usr/src/app + - ./back:/usr/src/back + - ./front:/usr/src/front + - ./pusher:/usr/src/pusher From cd7a332b4c011ce6a8cbc7c681a9d1ac6f365c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 15:48:25 +0200 Subject: [PATCH 02/10] Improving error throwing and handling in pusher/admin/front --- back/src/Services/AdminApi.ts | 2 +- front/src/Connexion/ConnectionManager.ts | 2 +- pusher/src/Controller/BaseController.ts | 16 ++++++++++++++-- pusher/src/Services/AdminApi.ts | 10 +++++----- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index ef969a76..0c53e3b3 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -24,7 +24,7 @@ class AdminApi { async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); + return Promise.reject(new Error('No admin backoffice set!')); } const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index ed920aa0..aa679b5e 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -69,7 +69,7 @@ class ConnectionManager { return Promise.resolve(new Room(roomId)); } - return Promise.reject('Invalid URL'); + return Promise.reject(new Error('Invalid URL')); } private async verifyToken(token: string): Promise { diff --git a/pusher/src/Controller/BaseController.ts b/pusher/src/Controller/BaseController.ts index bb500b57..91882138 100644 --- a/pusher/src/Controller/BaseController.ts +++ b/pusher/src/Controller/BaseController.ts @@ -13,8 +13,20 @@ export class BaseController { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any protected errorToResponse(e: any, res: HttpResponse): void { - console.error(e.message || "An error happened.", e?.config.url); - console.error(e.stack || 'no stack defined.'); + if (e && e.message) { + let url = e?.config?.url; + if (url !== undefined) { + url = ' for URL: '+url; + } else { + url = ''; + } + console.error('ERROR: '+e.message+url); + } else if (typeof(e) === 'string') { + console.error(e); + } + if (e.stack) { + console.error(e.stack); + } if (e.response) { res.writeStatus(e.response.status+" "+e.response.statusText); this.addCorsHeaders(res); diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index d50e2a4f..0cc6d43e 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -37,7 +37,7 @@ class AdminApi { async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); + return Promise.reject(new Error('No admin backoffice set!')); } const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { @@ -60,7 +60,7 @@ class AdminApi { async fetchMemberDataByUuid(uuid: string, roomId: string): Promise { if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); + return Promise.reject(new Error('No admin backoffice set!')); } const res = await Axios.get(ADMIN_API_URL+'/api/room/access', { params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } @@ -70,7 +70,7 @@ class AdminApi { async fetchMemberDataByToken(organizationMemberToken: string): Promise { if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); + return Promise.reject(new Error('No admin backoffice set!')); } //todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case. const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken, @@ -81,7 +81,7 @@ class AdminApi { async fetchCheckUserByToken(organizationMemberToken: string): Promise { if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); + return Promise.reject(new Error('No admin backoffice set!')); } //todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case. const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken, @@ -104,7 +104,7 @@ class AdminApi { async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise { if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); + return Promise.reject(new Error('No admin backoffice set!')); } //todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case. return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken, From 3ef1f0dc7b326c3cf602c7fbeb5e638cf6fa13cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 16:00:14 +0200 Subject: [PATCH 03/10] [Breaking change] UPLOADER_URL and ADMIN_URL are now true URLs (and not only host name) and API_URL is replaced by PUSHER_URL API_URL is still accepted for BC compatibility of the self-hosted deployments. This will allow deploying on a single domain, using a reverse proxy that acts on the path. This however means that the config needs to be changed for all WorkAdventure deployments. --- contrib/docker/docker-compose.prod.yaml | 2 +- deeployer.libsonnet | 6 +++--- docker-compose.single-domain.yaml | 2 +- docker-compose.yaml | 6 +++--- front/Dockerfile | 2 +- front/src/Administration/GlobalMessageManager.ts | 2 +- front/src/Connexion/ConnectionManager.ts | 8 ++++---- front/src/Connexion/Room.ts | 4 ++-- front/src/Connexion/RoomConnection.ts | 10 +++++++--- front/src/Enum/EnvironmentVariable.ts | 9 +++++---- front/webpack.config.js | 1 + maps/Dockerfile | 1 - 12 files changed, 29 insertions(+), 24 deletions(-) diff --git a/contrib/docker/docker-compose.prod.yaml b/contrib/docker/docker-compose.prod.yaml index c726ba84..6b3b8520 100644 --- a/contrib/docker/docker-compose.prod.yaml +++ b/contrib/docker/docker-compose.prod.yaml @@ -37,7 +37,7 @@ services: DEBUG_MODE: "$DEBUG_MODE" JITSI_URL: $JITSI_URL JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" - API_URL: pusher.${DOMAIN} + PUSHER_URL: //pusher.${DOMAIN} TURN_SERVER: "${TURN_SERVER}" TURN_USER: "${TURN_USER}" TURN_PASSWORD: "${TURN_PASSWORD}" diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 52cea293..07506f11 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -82,9 +82,9 @@ }, "ports": [80], "env": { - "API_URL": "pusher."+url, - "UPLOADER_URL": "uploader."+url, - "ADMIN_URL": url, + "PUSHER_URL": "//pusher."+url, + "UPLOADER_URL": "//uploader."+url, + "ADMIN_URL": "//"+url, "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 3ffecb60..e8842e8b 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -26,7 +26,7 @@ services: JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" HOST: "0.0.0.0" NODE_ENV: development - API_URL: /pusher + PUSHER_URL: /pusher UPLOADER_URL: /uploader ADMIN_URL: /admin MAPS_URL: /maps diff --git a/docker-compose.yaml b/docker-compose.yaml index 9ea637a3..504c5b23 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -26,9 +26,9 @@ services: JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" HOST: "0.0.0.0" NODE_ENV: development - API_URL: pusher.workadventure.localhost - UPLOADER_URL: uploader.workadventure.localhost - ADMIN_URL: workadventure.localhost + PUSHER_URL: //pusher.workadventure.localhost + UPLOADER_URL: //uploader.workadventure.localhost + ADMIN_URL: //workadventure.localhost STARTUP_COMMAND_1: ./templater.sh STARTUP_COMMAND_2: yarn install STUN_SERVER: "stun:stun.l.google.com:19302" diff --git a/front/Dockerfile b/front/Dockerfile index 51734535..ef724e0f 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -3,7 +3,7 @@ WORKDIR /var/www/messages COPY --chown=docker:docker messages . RUN yarn install && yarn proto -# we are rebuilding on each deploy to cope with the API_URL environment URL +# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL FROM thecodingmachine/nodejs:14-apache COPY --chown=docker:docker front . diff --git a/front/src/Administration/GlobalMessageManager.ts b/front/src/Administration/GlobalMessageManager.ts index f30329d9..bc9f3cfe 100644 --- a/front/src/Administration/GlobalMessageManager.ts +++ b/front/src/Administration/GlobalMessageManager.ts @@ -1,6 +1,6 @@ import {HtmlUtils} from "./../WebRtc/HtmlUtils"; import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager"; -import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; +import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "../Connexion/RoomConnection"; import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels"; diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index aa679b5e..04c93a3e 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -1,5 +1,5 @@ import Axios from "axios"; -import {API_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable"; +import {PUSHER_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "./RoomConnection"; import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels"; import {GameConnexionTypes, urlManager} from "../Url/UrlManager"; @@ -34,7 +34,7 @@ class ConnectionManager { this.connexionType = connexionType; if(connexionType === GameConnexionTypes.register) { const organizationMemberToken = urlManager.getOrganizationToken(); - const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data); + const data = await Axios.post(`${PUSHER_URL}/register`, {organizationMemberToken}).then(res => res.data); this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures); localUserStore.saveUser(this.localUser); @@ -73,11 +73,11 @@ class ConnectionManager { } private async verifyToken(token: string): Promise { - await Axios.get(`${API_URL}/verify`, {params: {token}}); + await Axios.get(`${PUSHER_URL}/verify`, {params: {token}}); } public async anonymousLogin(isBenchmark: boolean = false): Promise { - const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data); + const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then(res => res.data); this.localUser = new LocalUser(data.userUuid, data.authToken, []); if (!isBenchmark) { // In benchmark, we don't have a local storage. localUserStore.saveUser(this.localUser); diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 782d5edf..05d94440 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,5 +1,5 @@ import Axios from "axios"; -import {API_URL} from "../Enum/EnvironmentVariable"; +import {PUSHER_URL} from "../Enum/EnvironmentVariable"; export class Room { public readonly id: string; @@ -67,7 +67,7 @@ export class Room { // We have a private ID, we need to query the map URL from the server. const urlParts = this.parsePrivateUrl(this.id); - Axios.get(`${API_URL}/map`, { + Axios.get(`${PUSHER_URL}/map`, { params: urlParts }).then(({data}) => { console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 584dae06..0793851d 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -1,4 +1,4 @@ -import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; +import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; import Axios from "axios"; import { BatchMessage, @@ -67,7 +67,11 @@ export class RoomConnection implements RoomConnection { * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" */ public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) { - let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://'); + let url = PUSHER_URL; + if (url.startsWith('//')) { + url = window.location.protocol+url; + } + url = url.replace('http://', 'ws://').replace('https://', 'wss://'); url += '/room'; url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); url += '&token='+(token ?encodeURIComponent(token):''); @@ -381,7 +385,7 @@ export class RoomConnection implements RoomConnection { public onConnectError(callback: (error: Event) => void): void { this.socket.addEventListener('error', callback) } - + public onConnect(callback: (roomConnection: OnConnectInterface) => void): void { //this.socket.addEventListener('open', callback) this.onMessage(EventMessage.CONNECT, callback); diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 844bf564..ea2434af 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,8 +1,9 @@ const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true"; const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json'; -const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost"); -const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost'); -const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost"); +// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL +const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost"); +const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost'; +const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost"; const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302"; const TURN_SERVER: string = process.env.TURN_SERVER || ""; const TURN_USER: string = process.env.TURN_USER || ''; @@ -17,7 +18,7 @@ const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new export { DEBUG_MODE, START_ROOM_URL, - API_URL, + PUSHER_URL, UPLOADER_URL, ADMIN_URL, RESOLUTION, diff --git a/front/webpack.config.js b/front/webpack.config.js index aec70d4a..54d5fffc 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -70,6 +70,7 @@ module.exports = { }), new webpack.EnvironmentPlugin([ 'API_URL', + 'PUSHER_URL', 'UPLOADER_URL', 'ADMIN_URL', 'DEBUG_MODE', diff --git a/maps/Dockerfile b/maps/Dockerfile index 33ca48b5..7fc6fd19 100644 --- a/maps/Dockerfile +++ b/maps/Dockerfile @@ -1,4 +1,3 @@ -# we are rebuilding on each deploy to cope with the API_URL environment URL FROM thecodingmachine/nodejs:12-apache COPY --chown=docker:docker . . From f2e64c8763b4f47beb1442f144ee23320e308640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 16:20:11 +0200 Subject: [PATCH 04/10] Fixing missing API_URL warning --- front/webpack.config.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/front/webpack.config.js b/front/webpack.config.js index 54d5fffc..e1ece324 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -68,20 +68,20 @@ module.exports = { new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin([ - 'API_URL', - 'PUSHER_URL', - 'UPLOADER_URL', - 'ADMIN_URL', - 'DEBUG_MODE', - 'STUN_SERVER', - 'TURN_SERVER', - 'TURN_USER', - 'TURN_PASSWORD', - 'JITSI_URL', - 'JITSI_PRIVATE_MODE', - 'START_ROOM_URL' - ]) + new webpack.EnvironmentPlugin({ + 'API_URL': null, + 'PUSHER_URL': undefined, + 'UPLOADER_URL': null, + 'ADMIN_URL': null, + 'DEBUG_MODE': null, + 'STUN_SERVER': null, + 'TURN_SERVER': null, + 'TURN_USER': null, + 'TURN_PASSWORD': null, + 'JITSI_URL': null, + 'JITSI_PRIVATE_MODE': null, + 'START_ROOM_URL': null + }) ], }; From 11a1428c893ded65fc904253c1eda14447641907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 16:20:21 +0200 Subject: [PATCH 05/10] Fixing broken live-reload --- front/webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/front/webpack.config.js b/front/webpack.config.js index e1ece324..f1a3df44 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -12,6 +12,7 @@ module.exports = { devServer: { contentBase: './dist', host: '0.0.0.0', + sockPort: 80, disableHostCheck: true, historyApiFallback: { rewrites: [ From 651dfc63f416415afa9176910dc7aee7f0cda331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 16:38:30 +0200 Subject: [PATCH 06/10] Fixing TURN server config --- docker-compose.single-domain.yaml | 34 ++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index e8842e8b..0bd1dcb6 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -31,9 +31,12 @@ services: ADMIN_URL: /admin MAPS_URL: /maps STARTUP_COMMAND_1: yarn install - TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443" - TURN_USER: workadventure - TURN_PASSWORD: WorkAdventure123 + TURN_SERVER: "turn:localhost:3478,turns:localhost:5349" + # Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials. + # Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container + TURN_USER: "" + TURN_PASSWORD: "" + START_ROOM_URL: "$START_ROOM_URL" command: yarn run start volumes: - ./front:/usr/src/app @@ -177,3 +180,28 @@ services: - ./back:/usr/src/back - ./front:/usr/src/front - ./pusher:/usr/src/pusher + +# coturn: +# image: coturn/coturn:4.5.2 +# command: +# - turnserver +# #- -c=/etc/coturn/turnserver.conf +# - --log-file=stdout +# - --external-ip=$$(detect-external-ip) +# - --listening-port=3478 +# - --min-port=10000 +# - --max-port=10010 +# - --tls-listening-port=5349 +# - --listening-ip=0.0.0.0 +# - --realm=localhost +# - --server-name=localhost +# - --lt-cred-mech +# # Enable Coturn "REST API" to validate temporary passwords. +# #- --use-auth-secret +# #- --static-auth-secret=SomeStaticAuthSecret +# #- --userdb=/var/lib/turn/turndb +# - --user=workadventure:WorkAdventure123 +# # use real-valid certificate/privatekey files +# #- --cert=/root/letsencrypt/fullchain.pem +# #- --pkey=/root/letsencrypt/privkey.pem +# network_mode: host From e166c69b5877d96f4c55377e8bbf70a07ba3a659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 16:38:51 +0200 Subject: [PATCH 07/10] Fixing reading of the relative URL in RommConnection --- front/src/Connexion/RoomConnection.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 0793851d..199e8f6a 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -67,10 +67,7 @@ export class RoomConnection implements RoomConnection { * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" */ public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) { - let url = PUSHER_URL; - if (url.startsWith('//')) { - url = window.location.protocol+url; - } + let url = new URL(PUSHER_URL, window.location.toString()).toString(); url = url.replace('http://', 'ws://').replace('https://', 'wss://'); url += '/room'; url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); From adf2e60d54757815855e8b032b089d2b08314c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 31 Mar 2021 17:50:27 +0200 Subject: [PATCH 08/10] Fixing issue with double slash in websocket URL --- front/src/Connexion/RoomConnection.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 199e8f6a..9efd5433 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -69,7 +69,10 @@ export class RoomConnection implements RoomConnection { public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) { let url = new URL(PUSHER_URL, window.location.toString()).toString(); url = url.replace('http://', 'ws://').replace('https://', 'wss://'); - url += '/room'; + if (!url.endsWith('/')) { + url += '/'; + } + url += 'room'; url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); url += '&token='+(token ?encodeURIComponent(token):''); url += '&name='+encodeURIComponent(name); From f18562577e3d7d9b17cd23a728dc3d1401169028 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 18:46:01 +0000 Subject: [PATCH 09/10] Bump y18n from 3.2.1 to 3.2.2 in /pusher Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] --- pusher/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pusher/yarn.lock b/pusher/yarn.lock index 501146cb..43f58988 100644 --- a/pusher/yarn.lock +++ b/pusher/yarn.lock @@ -3032,9 +3032,9 @@ xtend@^4.0.0: integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== yallist@^3.0.0, yallist@^3.0.3: version "3.1.1" From 88cc15cd029b8185223b4486b3d4b3c1f14cb3d3 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Thu, 1 Apr 2021 16:43:12 +0200 Subject: [PATCH 10/10] FEATURE: editing a room in the admin trigger a refresh system --- back/src/Model/Admin.ts | 7 --- back/src/Model/GameRoom.ts | 18 +++---- back/src/RoomManager.ts | 6 ++- back/src/Services/AdminApi.ts | 49 ------------------- back/src/Services/SocketManager.ts | 52 +++++++++------------ front/src/Connexion/RoomConnection.ts | 2 + messages/protos/messages.proto | 9 ++++ pusher/src/Controller/AdminController.ts | 52 ++++++++++++++++++++- pusher/src/Controller/IoSocketController.ts | 4 +- pusher/src/Model/PusherRoom.ts | 16 +++++-- pusher/src/Services/AdminApi.ts | 10 +++- pusher/src/Services/SocketManager.ts | 41 +++++++++++----- 12 files changed, 148 insertions(+), 118 deletions(-) delete mode 100644 back/src/Services/AdminApi.ts diff --git a/back/src/Model/Admin.ts b/back/src/Model/Admin.ts index a121d105..0be74b85 100644 --- a/back/src/Model/Admin.ts +++ b/back/src/Model/Admin.ts @@ -1,9 +1,3 @@ -import { Group } from "./Group"; -import { PointInterface } from "./Websocket/PointInterface"; -import {Zone} from "_Model/Zone"; -import {Movable} from "_Model/Movable"; -import {PositionNotifier} from "_Model/PositionNotifier"; -import {ServerDuplexStream} from "grpc"; import { BatchMessage, PusherToBackMessage, @@ -11,7 +5,6 @@ import { ServerToClientMessage, SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage } from "../Messages/generated/messages_pb"; -import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; import {AdminSocket} from "../RoomManager"; diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 6a592ed0..c628d29d 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -38,12 +38,10 @@ export class GameRoom { private readonly positionNotifier: PositionNotifier; public readonly roomId: string; - public readonly anonymous: boolean; - public tags: string[]; - public policyType: GameRoomPolicyTypes; public readonly roomSlug: string; public readonly worldSlug: string = ''; public readonly organizationSlug: string = ''; + private versionNumber:number = 1; private nextUserId: number = 1; constructor(roomId: string, @@ -56,11 +54,8 @@ export class GameRoom { onLeaves: LeavesCallback) { this.roomId = roomId; - this.anonymous = isRoomAnonymous(roomId); - this.tags = []; - this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY; - if (this.anonymous) { + if (isRoomAnonymous(roomId)) { this.roomSlug = extractRoomSlugPublicRoomId(this.roomId); } else { const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId); @@ -304,10 +299,6 @@ export class GameRoom { return this.itemsState; } - public canAccess(userTags: string[]): boolean { - return arrayIntersect(userTags, this.tags); - } - public addZoneListener(call: ZoneSocket, x: number, y: number): Set { return this.positionNotifier.addZoneListener(call, x, y); } @@ -328,4 +319,9 @@ export class GameRoom { public adminLeave(admin: Admin): void { this.admins.delete(admin); } + + public incrementVersion(): number { + this.versionNumber++ + return this.versionNumber; + } } diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 60e90d82..54215698 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -10,7 +10,7 @@ import { JoinRoomMessage, PlayGlobalMessage, PusherToBackMessage, - QueryJitsiJwtMessage, + QueryJitsiJwtMessage, RefreshRoomPromptMessage, ServerToAdminClientMessage, ServerToClientMessage, SilentMessage, @@ -193,6 +193,10 @@ const roomManager: IRoomManagerServer = { socketManager.dispatchWorlFullWarning(call.request.getRoomid()); callback(null, new EmptyMessage()); }, + sendRefreshRoomPrompt(call: ServerUnaryCall, callback: sendUnaryData): void { + socketManager.dispatchRoomRefresh(call.request.getRoomid()); + callback(null, new EmptyMessage()); + }, }; export {roomManager}; diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts deleted file mode 100644 index 0c53e3b3..00000000 --- a/back/src/Services/AdminApi.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; -import Axios from "axios"; - -export interface AdminApiData { - organizationSlug: string - worldSlug: string - roomSlug: string - mapUrlStart: string - tags: string[] - policy_type: number - userUuid: string - messages?: unknown[], - textures: CharacterTexture[] -} - -export interface CharacterTexture { - id: number, - level: number, - url: string, - rights: string -} - -class AdminApi { - - async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { - if (!ADMIN_API_URL) { - return Promise.reject(new Error('No admin backoffice set!')); - } - - const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { - organizationSlug, - worldSlug - }; - - if (roomSlug) { - params.roomSlug = roomSlug; - } - - const res = await Axios.get(ADMIN_API_URL + '/api/map', - { - headers: {"Authorization": `${ADMIN_API_TOKEN}`}, - params - } - ) - return res.data; - } -} - -export const adminApi = new AdminApi(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c03f4773..16b4d005 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -26,7 +26,7 @@ import { GroupLeftZoneMessage, WorldFullWarningMessage, UserLeftZoneMessage, - BanUserMessage, + BanUserMessage, RefreshRoomMessage, } from "../Messages/generated/messages_pb"; import {User, UserSocket} from "../Model/User"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; @@ -41,7 +41,6 @@ import { } from "../Enum/EnvironmentVariable"; import {Movable} from "../Model/Movable"; import {PositionInterface} from "../Model/PositionInterface"; -import {adminApi, CharacterTexture} from "./AdminApi"; import Jwt from "jsonwebtoken"; import {JITSI_URL} from "../Enum/EnvironmentVariable"; import {clientEventsEmitter} from "./ClientEventsEmitter"; @@ -129,15 +128,7 @@ export class SocketManager { if (viewport === undefined) { throw new Error('Viewport not found in message'); } - - // sending to all clients in room except sender - /*client.position = { - x: position.x, - y: position.y, - direction, - moving: position.moving, - }; - client.viewport = viewport;*/ + // update position in the world room.updatePosition(user, ProtobufUtils.toPointInterface(position)); @@ -192,21 +183,6 @@ export class SocketManager { } } - // TODO: handle this message in pusher - /*async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) { - try { - const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid()); - if (!reportedSocket) { - throw 'reported socket user not found'; - } - //TODO report user on admin application - await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid) - } catch (e) { - console.error('An error occurred on "handleReportMessage"'); - console.error(e); - } - }*/ - emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void { //send only at user const remoteUser = room.getUsers().get(data.getReceiverid()); @@ -289,11 +265,6 @@ export class SocketManager { (thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener), (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener) ); - if (!world.anonymous) { - const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug) - world.tags = data.tags - world.policyType = Number(data.policy_type) - } gaugeManager.incNbRoomGauge(); this.rooms.set(roomId, world); } @@ -772,6 +743,25 @@ export class SocketManager { recipient.socket.write(clientMessage); }); } + + dispatchRoomRefresh(roomId: string,): void { + const room = this.rooms.get(roomId); + if (!room) { + return; + } + + const versionNumber = room.incrementVersion(); + room.getUsers().forEach((recipient) => { + const worldFullMessage = new RefreshRoomMessage(); + worldFullMessage.setRoomid(roomId) + worldFullMessage.setVersionnumber(versionNumber) + + const clientMessage = new ServerToClientMessage(); + clientMessage.setRefreshroommessage(worldFullMessage); + + recipient.socket.write(clientMessage); + }); + } } export const socketManager = new SocketManager(); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 9efd5433..0220cb52 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -187,6 +187,8 @@ export class RoomConnection implements RoomConnection { adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage); } else if (message.hasWorldfullwarningmessage()) { worldFullWarningStream.onMessage(); + } else if (message.hasRefreshroommessage()) { + //todo: implement a way to notify the user the room was refreshed. } else { throw new Error('Unknown message received'); } diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index cc23ed24..6f8ed036 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -202,6 +202,13 @@ message WorldFullWarningMessage{ message WorldFullWarningToRoomMessage{ string roomId = 1; } +message RefreshRoomPromptMessage{ + string roomId = 1; +} +message RefreshRoomMessage{ + string roomId = 1; + int32 versionNumber = 2; +} message WorldFullMessage{ } @@ -229,6 +236,7 @@ message ServerToClientMessage { AdminRoomMessage adminRoomMessage = 14; WorldFullWarningMessage worldFullWarningMessage = 15; WorldFullMessage worldFullMessage = 16; + RefreshRoomMessage refreshRoomMessage = 17; } } @@ -395,4 +403,5 @@ service RoomManager { rpc ban(BanMessage) returns (EmptyMessage); rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage); rpc sendWorldFullWarningToRoom(WorldFullWarningToRoomMessage) returns (EmptyMessage); + rpc sendRefreshRoomPrompt(RefreshRoomPromptMessage) returns (EmptyMessage); } diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts index 3f87fcf1..74d4e792 100644 --- a/pusher/src/Controller/AdminController.ts +++ b/pusher/src/Controller/AdminController.ts @@ -2,7 +2,7 @@ import {BaseController} from "./BaseController"; import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; import {apiClientRepository} from "../Services/ApiClientRepository"; -import {AdminRoomMessage, WorldFullWarningToRoomMessage} from "../Messages/generated/messages_pb"; +import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb"; export class AdminController extends BaseController{ @@ -11,6 +11,56 @@ export class AdminController extends BaseController{ super(); this.App = App; this.receiveGlobalMessagePrompt(); + this.receiveRoomEditionPrompt(); + } + + receiveRoomEditionPrompt() { + this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => { + this.addCorsHeaders(res); + res.end(); + }); + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => { + res.onAborted(() => { + console.warn('/message request was aborted'); + }) + + const token = req.getHeader('admin-token'); + const body = await res.json(); + + if (token !== ADMIN_API_TOKEN) { + console.error('Admin access refused for token: '+token) + res.writeStatus("401 Unauthorized").end('Incorrect token'); + return; + } + + try { + if (typeof body.roomId !== 'string') { + throw 'Incorrect roomId parameter' + } + const roomId: string = body.roomId; + + await apiClientRepository.getClient(roomId).then((roomClient) =>{ + return new Promise((res, rej) => { + const roomMessage = new RefreshRoomPromptMessage(); + roomMessage.setRoomid(roomId); + + roomClient.sendRefreshRoomPrompt(roomMessage, (err) => { + err ? rej(err) : res(); + }); + }); + }); + } catch (err) { + this.errorToResponse(err, res); + return; + } + + res.writeStatus("200"); + res.end('ok'); + + + }); } receiveGlobalMessagePrompt() { diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 85a80e11..d4b0f98e 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -190,10 +190,10 @@ export class IoSocketController { memberMessages = userData.messages; memberTags = userData.tags; memberTextures = userData.textures; - if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) { + if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) { throw new Error('No correct tags') } - if (!room.anonymous && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) { + if (!room.public && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) { throw new Error('No correct member') } } catch (e) { diff --git a/pusher/src/Model/PusherRoom.ts b/pusher/src/Model/PusherRoom.ts index 92ff87d1..dcea5859 100644 --- a/pusher/src/Model/PusherRoom.ts +++ b/pusher/src/Model/PusherRoom.ts @@ -13,21 +13,22 @@ export enum GameRoomPolicyTypes { export class PusherRoom { private readonly positionNotifier: PositionDispatcher; - public readonly anonymous: boolean; + public readonly public: boolean; public tags: string[]; public policyType: GameRoomPolicyTypes; public readonly roomSlug: string; public readonly worldSlug: string = ''; public readonly organizationSlug: string = ''; + private versionNumber: number = 1; constructor(public readonly roomId: string, private socketListener: ZoneEventListener) { - this.anonymous = isRoomAnonymous(roomId); + this.public = isRoomAnonymous(roomId); this.tags = []; this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY; - if (this.anonymous) { + if (this.public) { this.roomSlug = extractRoomSlugPublicRoomId(this.roomId); } else { const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId); @@ -55,4 +56,13 @@ export class PusherRoom { public isEmpty(): boolean { return this.positionNotifier.isEmpty(); } + + public needsUpdate(versionNumber: number): boolean { + if (this.versionNumber < versionNumber) { + this.versionNumber = versionNumber; + return true; + } else { + return false; + } + } } diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 0cc6d43e..bd8edeb6 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -1,5 +1,6 @@ import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; import Axios from "axios"; +import {GameRoomPolicyTypes} from "_Model/PusherRoom"; export interface AdminApiData { organizationSlug: string @@ -13,6 +14,13 @@ export interface AdminApiData { textures: CharacterTexture[] } +export interface MapDetailsData { + roomSlug: string, + mapUrl: string, + policy_type: GameRoomPolicyTypes, + tags: string[], +} + export interface AdminBannedData { is_banned: boolean, message: string @@ -35,7 +43,7 @@ export interface FetchMemberDataByUuidResponse { class AdminApi { - async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { + async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { if (!ADMIN_API_URL) { return Promise.reject(new Error('No admin backoffice set!')); } diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 9b698e38..f0734b94 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -22,7 +22,7 @@ import { WorldFullMessage, AdminPusherToBackMessage, ServerToAdminClientMessage, - UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage + UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage } from "../Messages/generated/messages_pb"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable"; @@ -54,7 +54,7 @@ export interface AdminSocketData { export class SocketManager implements ZoneEventListener { - private Worlds: Map = new Map(); + private rooms: Map = new Map(); private sockets: Map = new Map(); constructor() { @@ -180,6 +180,11 @@ export class SocketManager implements ZoneEventListener { // If this is the first message sent, send back the viewport. this.handleViewport(client, viewport); } + + if (message.hasRefreshroommessage()) { + const refreshMessage:RefreshRoomMessage = message.getRefreshroommessage() as unknown as RefreshRoomMessage; + this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber()) + } // Let's pass data over from the back to the client. if (!client.disconnecting) { @@ -219,7 +224,7 @@ export class SocketManager implements ZoneEventListener { try { client.viewport = viewport; - const world = this.Worlds.get(client.roomId); + const world = this.rooms.get(client.roomId); if (!world) { console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'"); return; @@ -310,12 +315,12 @@ export class SocketManager implements ZoneEventListener { if (socket.roomId) { try { //user leaves room - const room: PusherRoom | undefined = this.Worlds.get(socket.roomId); + const room: PusherRoom | undefined = this.rooms.get(socket.roomId); if (room) { debug('Leaving room %s.', socket.roomId); room.leave(socket); if (room.isEmpty()) { - this.Worlds.delete(socket.roomId); + this.rooms.delete(socket.roomId); debug('Room %s is empty. Deleting.', socket.roomId); } } else { @@ -339,19 +344,23 @@ export class SocketManager implements ZoneEventListener { async getOrCreateRoom(roomId: string): Promise { //check and create new world for a room - let world = this.Worlds.get(roomId) + let world = this.rooms.get(roomId) if(world === undefined){ world = new PusherRoom(roomId, this); - if (!world.anonymous) { - const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug) - world.tags = data.tags - world.policyType = Number(data.policy_type) + if (!world.public) { + await this.updateRoomWithAdminData(world); } - this.Worlds.set(roomId, world); + this.rooms.set(roomId, world); } return Promise.resolve(world) } + public async updateRoomWithAdminData(world: PusherRoom): Promise { + const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug) + world.tags = data.tags; + world.policyType = Number(data.policy_type); + } + emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) { const pusherToBackMessage = new PusherToBackMessage(); pusherToBackMessage.setPlayglobalmessage(playglobalmessage); @@ -360,7 +369,7 @@ export class SocketManager implements ZoneEventListener { } public getWorlds(): Map { - return this.Worlds; + return this.rooms; } searchClientByUuid(uuid: string): ExSocketInterface | null { @@ -544,6 +553,14 @@ export class SocketManager implements ZoneEventListener { client.send(serverToClientMessage.serializeBinary().buffer, true); } + + private refreshRoomData(roomId: string, versionNumber: number): void { + const room = this.rooms.get(roomId); + //this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed. + if (!room || !room.needsUpdate(versionNumber)) return; + + this.updateRoomWithAdminData(room); + } } export const socketManager = new SocketManager();