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/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 new file mode 100644 index 00000000..0bd1dcb6 --- /dev/null +++ b/docker-compose.single-domain.yaml @@ -0,0 +1,207 @@ +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 + PUSHER_URL: /pusher + UPLOADER_URL: /uploader + ADMIN_URL: /admin + MAPS_URL: /maps + STARTUP_COMMAND_1: yarn install + 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 + 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 + +# 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 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 ed920aa0..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); @@ -69,15 +69,15 @@ 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 { - 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..9efd5433 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,8 +67,12 @@ 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://'); - url += '/room'; + let url = new URL(PUSHER_URL, window.location.toString()).toString(); + url = url.replace('http://', 'ws://').replace('https://', 'wss://'); + if (!url.endsWith('/')) { + url += '/'; + } + url += 'room'; url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); url += '&token='+(token ?encodeURIComponent(token):''); url += '&name='+encodeURIComponent(name); @@ -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..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: [ @@ -68,19 +69,20 @@ module.exports = { new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin([ - 'API_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 + }) ], }; 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 . . 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,