diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..f579acb1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +**/node_modules/** +**/Dockerfile diff --git a/.env.template b/.env.template index 330f3865..a83bd171 100644 --- a/.env.template +++ b/.env.template @@ -6,3 +6,7 @@ JITSI_ISS= SECRET_JITSI_KEY= ADMIN_API_TOKEN=123 START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json +# If your Turn server is configured to use the Turn REST API, you should put the shared auth secret here. +# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file. +# Keep empty if you are sharing hard coded / clear text credentials. +TURN_STATIC_AUTH_SECRET= diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 5b0c711e..ce186cec 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -150,6 +150,7 @@ jobs: JITSI_ISS: ${{ secrets.JITSI_ISS }} JITSI_URL: ${{ secrets.JITSI_URL }} SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }} + TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }} with: namespace: workadventure-${{ env.GITHUB_REF_SLUG }} @@ -159,34 +160,5 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com + msg: Environment deployed at https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com check_for_duplicate_msg: true - - - name: Run Cypress tests - 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 - with: - env: host=play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80 - spec: cypress/integration/spec.js - wait-on: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com - working-directory: e2e - - - name: Run Cypress tests in prod - uses: cypress-io/github-action@v2 - if: ${{ env.GITHUB_REF_SLUG == 'master' }} - env: - CYPRESS_BASE_URL: https://play.workadventu.re - with: - env: host=play.workadventu.re - spec: cypress/integration/spec.js - wait-on: https://workadventu.re - working-directory: e2e - - - name: "Upload the screenshot on test failure" - uses: actions/upload-artifact@v1 - if: failure() - with: - name: "screenshot" - path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png" diff --git a/README.md b/README.md index faafed98..a8c186b6 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Demo here : [https://workadventu.re/](https://workadventu.re/). # Work Adventure -## Work in progress - Work Adventure is a web-based collaborative workspace for small to medium teams (2-100 people) presented in the form of a 16-bit video game. @@ -15,7 +13,7 @@ In Work Adventure, you can move around your office and talk to your colleagues ( triggered when you move next to a colleague). -## Getting started +## Setting up a development environment Install Docker. @@ -101,5 +99,7 @@ Vagrant destroy * `Vagrant halt`: stop your VM Vagrant. * `Vagrant destroy`: delete your VM Vagrant. -## Features developed -You have more details of features developed in back [README.md](./back/README.md). +## Setting up a production environment + +The way you set up your production environment will highly depend on your servers. +We provide a production ready `docker-compose` file that you can use as a good starting point in the [contrib/docker](https://github.com/thecodingmachine/workadventure/tree/master/contrib/docker) directory. diff --git a/back/Dockerfile b/back/Dockerfile index 5ec83a8f..e95145cd 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -1,16 +1,26 @@ -FROM thecodingmachine/workadventure-back-base:latest as builder -WORKDIR /var/www/messages -COPY --chown=docker:docker messages . +# protobuf build +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder +WORKDIR /usr/src +COPY messages . RUN yarn install && yarn proto -FROM thecodingmachine/nodejs:12 - -COPY --chown=docker:docker back . -COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated +# typescript build +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2 +WORKDIR /usr/src +COPY back/yarn.lock back/package.json ./ RUN yarn install - +COPY back . +COPY --from=builder /usr/src/generated src/Messages/generated ENV NODE_ENV=production RUN yarn run tsc -CMD ["yarn", "run", "runprod"] +# final production image +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 +WORKDIR /usr/src +COPY back/yarn.lock back/package.json ./ +COPY --from=builder2 /usr/src/dist /usr/src/dist +ENV NODE_ENV=production +RUN yarn install --production +USER node +CMD ["yarn", "run", "runprod"] diff --git a/back/README.md b/back/README.md deleted file mode 100644 index 8a78f403..00000000 --- a/back/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Back Features - -## Login -To start your game, you must authenticate on the server back. -When you are authenticated, the back server return token and room starting. -``` -POST => /login -Params : - email: email of user. -``` - -## Join a room -When a user is connected, the user can join a room. -So you must send emit `join-room` with information user: -``` -Socket.io => 'join-room' - - userId: user id of gamer - roomId: room id when user enter in game - position: { - x: position x on map - y: position y on map - } -``` -All data users are stocked on socket client. - -## Send position user -When user move on the map, you can share new position on back with event `user-position`. -The information sent: -``` -Socket.io => 'user-position' - - userId: user id of gamer - roomId: room id when user enter in game - position: { - x: position x on map - y: position y on map - } -``` -All data users are updated on socket client. - -## Receive positions of all users -The application sends position of all users in each room in every few 10 milliseconds. -The data will pushed on event `user-position`: -``` -Socket.io => 'user-position' - - [ - { - userId: user id of gamer - roomId: room id when user enter in game - position: { - x: position x on map - y: position y on map - } - }, - ... - ] -``` - -[<<< back](../README.md) \ No newline at end of file diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 2cbfbf2e..b12f0542 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -1,4 +1,3 @@ -const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY"; 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; @@ -12,9 +11,9 @@ const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080; const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051; export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed +export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || ''; export { - SECRET_KEY, MINIMUM_DISTANCE, ADMIN_API_URL, ADMIN_API_TOKEN, diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index 9c46a41b..3e2dd3e0 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -100,11 +100,12 @@ class AdminApi { return res.data; } - reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) { + reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) { return Axios.post(`${ADMIN_API_URL}/api/report`, { reportedUserUuid, reportedUserComment, reporterUserUuid, + reportWorldSlug, }, { headers: {"Authorization": `${ADMIN_API_TOKEN}`} diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c90b51cf..3d6906ea 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -28,7 +28,13 @@ import {User, UserSocket} from "../Model/User"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {Group} from "../Model/Group"; import {cpuTracker} from "./CpuTracker"; -import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable"; +import { + GROUP_RADIUS, + JITSI_ISS, + MINIMUM_DISTANCE, + SECRET_JITSI_KEY, + TURN_STATIC_AUTH_SECRET +} from "../Enum/EnvironmentVariable"; import {Movable} from "../Model/Movable"; import {PositionInterface} from "../Model/PositionInterface"; import {adminApi, CharacterTexture} from "./AdminApi"; @@ -40,6 +46,8 @@ import {ZoneSocket} from "../RoomManager"; import {Zone} from "_Model/Zone"; import Debug from "debug"; import {Admin} from "_Model/Admin"; +import crypto from "crypto"; + const debug = Debug('sockermanager'); @@ -275,6 +283,12 @@ export class SocketManager { const webrtcSignalToClient = new WebRtcSignalToClientMessage(); webrtcSignalToClient.setUserid(user.id); webrtcSignalToClient.setSignal(data.getSignal()); + // TODO: only compute credentials if data.signal.type === "offer" + if (TURN_STATIC_AUTH_SECRET !== '') { + const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + webrtcSignalToClient.setWebrtcusername(username); + webrtcSignalToClient.setWebrtcpassword(password); + } const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient); @@ -295,6 +309,12 @@ export class SocketManager { const webrtcSignalToClient = new WebRtcSignalToClientMessage(); webrtcSignalToClient.setUserid(user.id); webrtcSignalToClient.setSignal(data.getSignal()); + // TODO: only compute credentials if data.signal.type === "offer" + if (TURN_STATIC_AUTH_SECRET !== '') { + const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + webrtcSignalToClient.setWebrtcusername(username); + webrtcSignalToClient.setWebrtcpassword(password); + } const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient); @@ -487,6 +507,11 @@ export class SocketManager { webrtcStartMessage1.setUserid(otherUser.id); webrtcStartMessage1.setName(otherUser.name); webrtcStartMessage1.setInitiator(true); + if (TURN_STATIC_AUTH_SECRET !== '') { + const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET); + webrtcStartMessage1.setWebrtcusername(username); + webrtcStartMessage1.setWebrtcpassword(password); + } const serverToClientMessage1 = new ServerToClientMessage(); serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1); @@ -500,6 +525,11 @@ export class SocketManager { webrtcStartMessage2.setUserid(user.id); webrtcStartMessage2.setName(user.name); webrtcStartMessage2.setInitiator(false); + if (TURN_STATIC_AUTH_SECRET !== '') { + const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + webrtcStartMessage2.setWebrtcusername(username); + webrtcStartMessage2.setWebrtcpassword(password); + } const serverToClientMessage2 = new ServerToClientMessage(); serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2); @@ -512,6 +542,25 @@ export class SocketManager { } } + /** + * Computes a unique user/password for the TURN server, using a shared secret between the WorkAdventure API server + * and the Coturn server. + * The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey` + */ + private getTURNCredentials(name: string, secret: string): {username: string, password: string} { + const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600; // this credential would be valid for the next 4 hours + const username = [unixTimeStamp, name].join(':'); + const hmac = crypto.createHmac('sha1', secret); + hmac.setEncoding('base64'); + hmac.write(username); + hmac.end(); + const password = hmac.read(); + return { + username: username, + password: password + }; + } + //disconnect user private disConnectedUser(user: User, group: Group) { // Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection diff --git a/contrib/docker/.env.prod.template b/contrib/docker/.env.prod.template new file mode 100644 index 00000000..c0c10181 --- /dev/null +++ b/contrib/docker/.env.prod.template @@ -0,0 +1,20 @@ +# The base domain +DOMAIN=workadventure.localhost + +DEBUG_MODE=false +JITSI_URL=meet.jit.si +# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret +JITSI_PRIVATE_MODE=false +JITSI_ISS= +SECRET_JITSI_KEY= + +# URL of the TURN server (needed to "punch a hole" through some networks for P2P connections) +TURN_SERVER= +TURN_USER= +TURN_PASSWORD= + +# The URL used by default, in the form: "/_/global/map/url.json" +START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json + +# The email address used by Let's encrypt to send renewal warnings (compulsory) +ACME_EMAIL= diff --git a/contrib/docker/docker-compose.prod.yaml b/contrib/docker/docker-compose.prod.yaml new file mode 100644 index 00000000..22860748 --- /dev/null +++ b/contrib/docker/docker-compose.prod.yaml @@ -0,0 +1,100 @@ +version: "3.3" +services: + reverse-proxy: + image: traefik:v2.3 + command: + - --log.level=WARN + #- --api.insecure=true + - --providers.docker + - --entryPoints.web.address=:80 + - --entrypoints.web.http.redirections.entryPoint.to=websecure + - --entrypoints.web.http.redirections.entryPoint.scheme=https + - --entryPoints.websecure.address=:443 + - --certificatesresolvers.myresolver.acme.email=d.negrier@thecodingmachine.com + - --certificatesresolvers.myresolver.acme.storage=/acme.json + # used during the challenge + - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web + ports: + - "80:80" + - "443:443" + # The Web UI (enabled by --api.insecure=true) + #- "8080:8080" + depends_on: + - pusher + - front + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./acme.json:/acme.json + restart: unless-stopped + + + front: + build: + context: ../.. + dockerfile: front/Dockerfile + #image: thecodingmachine/workadventure-front:master + environment: + DEBUG_MODE: "$DEBUG_MODE" + JITSI_URL: $JITSI_URL + JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" + API_URL: pusher.${DOMAIN} + TURN_SERVER: "${TURN_SERVER}" + TURN_USER: "${TURN_USER}" + TURN_PASSWORD: "${TURN_PASSWORD}" + START_ROOM_URL: "${START_ROOM_URL}" + labels: + - "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)" + - "traefik.http.routers.front.entryPoints=web,traefik" + - "traefik.http.services.front.loadbalancer.server.port=80" + - "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)" + - "traefik.http.routers.front-ssl.entryPoints=websecure" + - "traefik.http.routers.front-ssl.tls=true" + - "traefik.http.routers.front-ssl.service=front" + - "traefik.http.routers.front-ssl.tls.certresolver=myresolver" + restart: unless-stopped + + pusher: + build: + context: ../.. + dockerfile: pusher/Dockerfile + #image: thecodingmachine/workadventure-pusher:master + command: yarn run runprod + environment: + SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" + SECRET_KEY: yourSecretKey + API_URL: back:50051 + JITSI_URL: $JITSI_URL + JITSI_ISS: $JITSI_ISS + labels: + - "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)" + - "traefik.http.routers.pusher.entryPoints=web,traefik" + - "traefik.http.services.pusher.loadbalancer.server.port=8080" + - "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)" + - "traefik.http.routers.pusher-ssl.entryPoints=websecure" + - "traefik.http.routers.pusher-ssl.tls=true" + - "traefik.http.routers.pusher-ssl.service=pusher" + - "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver" + restart: unless-stopped + + back: + build: + context: ../.. + dockerfile: back/Dockerfile + #image: thecodingmachine/workadventure-back:master + command: yarn run runprod + environment: + SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" + ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" + ADMIN_API_URL: "$ADMIN_API_URL" + JITSI_URL: $JITSI_URL + JITSI_ISS: $JITSI_ISS + labels: + - "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)" + - "traefik.http.routers.back.entryPoints=web" + - "traefik.http.services.back.loadbalancer.server.port=8080" + - "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)" + - "traefik.http.routers.back-ssl.entryPoints=websecure" + - "traefik.http.routers.back-ssl.tls=true" + - "traefik.http.routers.back-ssl.service=back" + - "traefik.http.routers.back-ssl.tls.certresolver=myresolver" + restart: unless-stopped diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 89571945..5093c86a 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -3,7 +3,8 @@ 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://"+url else null, + // develop branch does not use admin because of issue with SSL certificate of admin as of now. + local adminUrl = if namespace == "master" /*|| namespace == "develop"*/ || std.startsWith(namespace, "admin") then "https://"+url else null, "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", "version": "1.0", "containers": { @@ -21,6 +22,7 @@ "JITSI_ISS": env.JITSI_ISS, "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, + "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET, } + if adminUrl != null then { "ADMIN_API_URL": adminUrl, } else {} @@ -39,6 +41,7 @@ "JITSI_ISS": env.JITSI_ISS, "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, + "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET, } + if adminUrl != null then { "ADMIN_API_URL": adminUrl, } else {} @@ -79,6 +82,7 @@ "TURN_USER": "workadventure", "TURN_PASSWORD": "WorkAdventure123", "JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false", + "START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json" //"GA_TRACKING_ID": "UA-10196481-11" } }, diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml deleted file mode 100644 index bdf5855a..00000000 --- a/docker-compose.ci.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3' - -services: - - wait_app: - image: dadarek/wait-for-dependencies - depends_on: - - reverse-proxy - command: front:8080 - cypress: - # the Docker image to use from https://github.com/cypress-io/cypress-docker-images - image: "cypress/included:3.8.3" - depends_on: - - reverse-proxy - environment: - # pass base url to test pointing at the web application - - CYPRESS_baseUrl=http://front:8080 - working_dir: /e2e - volumes: - - ./e2e/:/e2e \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 286c12ba..98071437 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -31,9 +31,12 @@ services: ADMIN_URL: workadventure.localhost STARTUP_COMMAND_1: ./templater.sh STARTUP_COMMAND_2: yarn install - TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443" - TURN_USER: workadventure - TURN_PASSWORD: WorkAdventure123 + STUN_SERVER: "stun:stun.l.google.com:19302" + TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.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: @@ -108,6 +111,7 @@ services: ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS + TURN_STATIC_AUTH_SECRET: SomeStaticAuthSecret volumes: - ./back:/usr/src/app labels: @@ -149,3 +153,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=coturn.workadventure.localhost +# - --server-name=coturn.workadventure.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/e2e/.gitignore b/e2e/.gitignore deleted file mode 100644 index 92fb84fd..00000000 --- a/e2e/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -screenshots/ -videos/ -node_modules/ \ No newline at end of file diff --git a/e2e/CYPRESS.md b/e2e/CYPRESS.md deleted file mode 100644 index dce9d698..00000000 --- a/e2e/CYPRESS.md +++ /dev/null @@ -1,36 +0,0 @@ -# Testing with cypress - -This project use [cypress](https://www.cypress.io/) to do functional testing of the website. -Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc. - -## Getting Started - -You will need to install theses dependancies on linux (don't know about mac): - -```bash -sudo apt update -sudo apt install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -``` - -Cypress can be installed locally in the e2e directory -```bash -cd e2e -npm install -``` - - -How to use: -```bash -npm run cy:run -npm run cy:open -``` - -The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow - -[More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server) - -## How to test a game - -Cypress cannot "see" and so cannot directly manipulate the canva created by Phaser. - -This means we have to do workarounds such as exposing core objects in the window so that cypress can manipulate them or doing console that cypress can catch. \ No newline at end of file diff --git a/e2e/cypress.json b/e2e/cypress.json deleted file mode 100644 index cea4979b..00000000 --- a/e2e/cypress.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "baseUrl": "http://workadventure.localhost", - "video": false, - "defaultCommandTimeout": 20000, - "pluginsFile": false, - "supportFile": false -} \ No newline at end of file diff --git a/e2e/cypress/integration/spec.js b/e2e/cypress/integration/spec.js deleted file mode 100644 index 7f94a502..00000000 --- a/e2e/cypress/integration/spec.js +++ /dev/null @@ -1,25 +0,0 @@ -Cypress.on('window:before:load', (win) => { - // because this is called before any scripts - // have loaded - the ga function is undefined - // so we need to create it. - win.cypressAsserter = cy.stub().as('ca') -}) - -describe('WorkAdventureGame', () => { - beforeEach(() => { - cy.visit('/', { - onBeforeLoad (win) { - cy.spy(win.console, 'log').as('console.log') - }, - }) - - }); - - it('loads', () => { - cy.get('@console.log').should('be.calledWith', 'Started the game') - cy.get('@console.log').should('be.calledWith', 'Preloading') - cy.get('@console.log').should('be.calledWith', 'Preloading done') - cy.get('@console.log').should('be.calledWith', 'startInit') - cy.get('@console.log').should('be.calledWith', 'startInit done') - }); -}); \ No newline at end of file diff --git a/e2e/package-lock.json b/e2e/package-lock.json deleted file mode 100644 index bfd6255c..00000000 --- a/e2e/package-lock.json +++ /dev/null @@ -1,1406 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@cypress/listr-verbose-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", - "integrity": "sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=", - "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "date-fns": "1.30.1", - "figures": "1.7.0" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "requires": { - "debug": "3.2.6", - "lodash.once": "4.1.1" - } - }, - "@types/sizzle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", - "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" - }, - "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", - "requires": { - "fast-deep-equal": "3.1.1", - "fast-json-stable-stringify": "2.1.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "arch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", - "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "2.1.2" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "4.17.15" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "0.14.5" - } - }, - "bluebird": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", - "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "cachedir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-1.3.0.tgz", - "integrity": "sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg==", - "requires": { - "os-homedir": "1.0.2" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "1.9.3" - } - } - } - }, - "check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=" - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "requires": { - "restore-cursor": "1.0.1" - } - }, - "cli-spinners": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", - "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=" - }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "requires": { - "slice-ansi": "0.0.4", - "string-width": "1.0.2" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, - "common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "1.1.1", - "inherits": "2.0.4", - "readable-stream": "2.3.7", - "typedarray": "0.0.6" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.7.1", - "shebang-command": "1.2.0", - "which": "1.3.1" - } - }, - "cypress": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.8.3.tgz", - "integrity": "sha512-I9L/d+ilTPPA4vq3NC1OPKmw7jJIpMKNdyfR8t1EXYzYCjyqbc59migOm1YSse/VRbISLJ+QGb5k4Y3bz2lkYw==", - "requires": { - "@cypress/listr-verbose-renderer": "0.4.1", - "@cypress/xvfb": "1.2.4", - "@types/sizzle": "2.3.2", - "arch": "2.1.1", - "bluebird": "3.5.0", - "cachedir": "1.3.0", - "chalk": "2.4.2", - "check-more-types": "2.24.0", - "commander": "2.15.1", - "common-tags": "1.8.0", - "debug": "3.2.6", - "eventemitter2": "4.1.2", - "execa": "0.10.0", - "executable": "4.1.1", - "extract-zip": "1.6.7", - "fs-extra": "5.0.0", - "getos": "3.1.1", - "is-ci": "1.2.1", - "is-installed-globally": "0.1.0", - "lazy-ass": "1.6.0", - "listr": "0.12.0", - "lodash": "4.17.15", - "log-symbols": "2.2.0", - "minimist": "1.2.0", - "moment": "2.24.0", - "ramda": "0.24.1", - "request": "2.88.0", - "request-progress": "3.0.0", - "supports-color": "5.5.0", - "tmp": "0.1.0", - "untildify": "3.0.3", - "url": "0.11.0", - "yauzl": "2.10.0" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "2.1.2" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" - } - }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eventemitter2": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz", - "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=" - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "requires": { - "cross-spawn": "6.0.5", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "requires": { - "pify": "2.3.0" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "requires": { - "fd-slicer": "1.0.1" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "requires": { - "pend": "1.2.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.8", - "mime-types": "2.1.26" - } - }, - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "4.0.0", - "universalify": "0.1.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "getos": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.1.1.tgz", - "integrity": "sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg==", - "requires": { - "async": "2.6.1" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "requires": { - "ini": "1.3.5" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "6.11.0", - "har-schema": "2.0.0" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.16.1" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "2.0.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "requires": { - "ci-info": "1.6.0" - } - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "requires": { - "global-dirs": "0.1.1", - "is-path-inside": "1.0.1" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "4.2.3" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=" - }, - "listr": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", - "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", - "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "figures": "1.7.0", - "indent-string": "2.1.0", - "is-promise": "2.1.0", - "is-stream": "1.1.0", - "listr-silent-renderer": "1.1.1", - "listr-update-renderer": "0.2.0", - "listr-verbose-renderer": "0.4.1", - "log-symbols": "1.0.2", - "log-update": "1.0.2", - "ora": "0.2.3", - "p-map": "1.2.0", - "rxjs": "5.5.12", - "stream-to-observable": "0.1.0", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "requires": { - "chalk": "1.1.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=" - }, - "listr-update-renderer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", - "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", - "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "elegant-spinner": "1.0.1", - "figures": "1.7.0", - "indent-string": "3.2.0", - "log-symbols": "1.0.2", - "log-update": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "requires": { - "chalk": "1.1.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "listr-verbose-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", - "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", - "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "date-fns": "1.30.1", - "figures": "1.7.0" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "requires": { - "chalk": "2.4.2" - } - }, - "log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", - "requires": { - "ansi-escapes": "1.4.0", - "cli-cursor": "1.0.2" - } - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "requires": { - "mime-db": "1.43.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "2.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" - }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", - "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-spinners": "0.1.2", - "object-assign": "4.1.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "ramda": { - "version": "0.24.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", - "integrity": "sha1-w7d1UZfzW43DUCIoJixMkd22uFc=" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "isarray": "1.0.0", - "process-nextick-args": "2.0.1", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "requires": { - "is-finite": "1.0.2" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.9.1", - "caseless": "0.12.0", - "combined-stream": "1.0.8", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.3", - "har-validator": "5.1.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.26", - "oauth-sign": "0.9.0", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.4.3", - "tunnel-agent": "0.6.0", - "uuid": "3.4.0" - } - }, - "request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", - "requires": { - "throttleit": "1.0.0" - } - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "7.1.6" - } - }, - "rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", - "requires": { - "symbol-observable": "1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "0.2.4", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" - } - }, - "stream-to-observable": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", - "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "3.0.0" - } - }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" - }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" - }, - "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", - "requires": { - "rimraf": "2.7.1" - } - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "1.7.0", - "punycode": "1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "2.1.1" - } - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "requires": { - "buffer-crc32": "0.2.13", - "fd-slicer": "1.1.0" - }, - "dependencies": { - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "requires": { - "pend": "1.2.0" - } - } - } - } - } -} diff --git a/e2e/package.json b/e2e/package.json deleted file mode 100644 index 97bd098c..00000000 --- a/e2e/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "dependencies": { - "cypress": "^3.8.3" - }, - "scripts": { - "cy:run": "cypress run", - "cy:open": "cypress open" - } -} diff --git a/front/dist/.gitignore b/front/dist/.gitignore new file mode 100644 index 00000000..64233a9e --- /dev/null +++ b/front/dist/.gitignore @@ -0,0 +1 @@ +index.html \ No newline at end of file diff --git a/front/dist/index.html.tmpl b/front/dist/index.tmpl.html similarity index 100% rename from front/dist/index.html.tmpl rename to front/dist/index.tmpl.html diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index 88c76ca2..665d4316 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -15,6 +15,14 @@ #gameMenu section { margin: 10px; } + section#socialLinks{ + position: absolute; + margin-bottom: 0; + } + section#socialLinks img{ + width: 32px; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; + } diff --git a/front/dist/resources/html/gameReport.html b/front/dist/resources/html/gameReport.html new file mode 100644 index 00000000..59ca3592 --- /dev/null +++ b/front/dist/resources/html/gameReport.html @@ -0,0 +1,122 @@ + + +
+
+ +

Moderate

+

What action do you want to take?

+
+
+

Block:

+

Block any communication from and to this user. This can be reverted.

+
+ +
+
+
+

Report:

+

Send a report message to the administrators of this room. They may later ban this user.

+
+
+
Your message:
+ +

+
+
+ +
+
+
+
+ diff --git a/front/dist/resources/html/gameShare.html b/front/dist/resources/html/gameShare.html index 4e487328..21c65014 100644 --- a/front/dist/resources/html/gameShare.html +++ b/front/dist/resources/html/gameShare.html @@ -14,9 +14,6 @@ width: 298px; height: 150px; } - #gameShare .cautiousText { - font-size: 50%; - } #gameShare h1 { background-image: linear-gradient(top, #f1f3f3, #d4dae0); border-bottom: 1px solid #a6abaf; diff --git a/front/dist/resources/logos/blockSign.svg b/front/dist/resources/logos/blockSign.svg new file mode 100644 index 00000000..c64ba294 --- /dev/null +++ b/front/dist/resources/logos/blockSign.svg @@ -0,0 +1,22 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/front/dist/resources/logos/blockingIcon.png b/front/dist/resources/logos/blockingIcon.png new file mode 100644 index 00000000..ef5f66cc Binary files /dev/null and b/front/dist/resources/logos/blockingIcon.png differ diff --git a/front/dist/resources/logos/cancel.png b/front/dist/resources/logos/cancel.png new file mode 100644 index 00000000..5bf9b6d2 Binary files /dev/null and b/front/dist/resources/logos/cancel.png differ diff --git a/front/dist/resources/logos/logo.png b/front/dist/resources/logos/logo.png new file mode 100644 index 00000000..f4440ad5 Binary files /dev/null and b/front/dist/resources/logos/logo.png differ diff --git a/front/dist/resources/logos/report.back.svg b/front/dist/resources/logos/report.back.svg new file mode 100644 index 00000000..1cb3b068 --- /dev/null +++ b/front/dist/resources/logos/report.back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/dist/resources/logos/report.svg b/front/dist/resources/logos/report.svg index 1cb3b068..14753256 100644 --- a/front/dist/resources/logos/report.svg +++ b/front/dist/resources/logos/report.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/front/dist/resources/objects/facebook-icon.png b/front/dist/resources/objects/facebook-icon.png new file mode 100644 index 00000000..7b74b9bf Binary files /dev/null and b/front/dist/resources/objects/facebook-icon.png differ diff --git a/front/dist/resources/objects/talk.png b/front/dist/resources/objects/talk.png index b9ecdb30..bc06d3b0 100644 Binary files a/front/dist/resources/objects/talk.png and b/front/dist/resources/objects/talk.png differ diff --git a/front/dist/resources/objects/twitter-icon.png b/front/dist/resources/objects/twitter-icon.png new file mode 100644 index 00000000..f2fa90f1 Binary files /dev/null and b/front/dist/resources/objects/twitter-icon.png differ diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 2e2c6c10..4bf05455 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -39,6 +39,7 @@ body .message-info.warning{ position: relative; transition: all 0.2s ease; background-color: #00000099; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; } .video-container i{ position: absolute; @@ -53,25 +54,71 @@ body .message-info.warning{ font-size: 28px; color: white; } -.video-container img.active{ - display: block; -} + .video-container img{ position: absolute; display: none; - width: 15px; - height: 15px; - background: #d93025; - border-radius: 48px; + width: 25px; + height: 25px; left: 5px; bottom: 5px; padding: 10px; z-index: 2; } +.video-container img.block-logo { + left: 30%; + bottom: 15%; + width: 150px; + height: 150px; +} -.video-container img.report{ +.video-container button.report{ + display: block; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; + background: none; + background-color: rgba(0, 0, 0, 0); + border: none; + background-color: black; + border-radius: 15px; + position: absolute; + width: 0px; + height: 35px; right: 5px; - left: auto; + bottom: 5px; + padding: 0px; + overflow: hidden; + z-index: 2; + transition: all .5s ease; +} + +.video-container:hover button.report{ + width: 35px; + padding: 10px; +} + +.video-container button.report:hover { + width: 150px; +} + +.video-container button.report img{ + position: absolute; + display: block; + bottom: 5px; + left: 5px; + margin: 0; + padding: 0; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; +} +.video-container button.report span{ + position: absolute; + bottom: 8px; + left: 36px; + color: white; + font-size: 16px; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; +} +.video-container img.active { + display: block !important; } .video-container video{ @@ -150,10 +197,7 @@ video#myCamVideo{ transition: all .2s; right: 224px; } -/*.btn-call{ - transition: all .1s; - left: 0px; -}*/ + .btn-cam-action div img{ height: 22px; width: 30px; @@ -352,6 +396,7 @@ body { #cowebsite { position: fixed; transition: transform 0.5s; + background-color: white; } #cowebsite.loading { background-color: gray; @@ -1078,7 +1123,7 @@ div.modal-report-user{ white-space: pre-wrap; word-wrap: break-word; } -.discussion .messages .message p.a{ +.discussion .messages .message p a{ color: white; } diff --git a/front/package.json b/front/package.json index 9776f0fd..0e50ba80 100644 --- a/front/package.json +++ b/front/package.json @@ -26,9 +26,10 @@ "axios": "^0.21.1", "generic-type-guard": "^3.2.0", "google-protobuf": "^3.13.0", - "phaser": "^3.52.0", + "phaser": "3.24.1", "queue-typescript": "^1.0.1", "quill": "^1.3.7", + "rxjs": "^6.6.3", "simple-peer": "^9.6.2", "socket.io-client": "^2.3.0", "webpack-require-http": "^0.4.3" diff --git a/front/src/Administration/ConsoleGlobalMessageManager.ts b/front/src/Administration/ConsoleGlobalMessageManager.ts index 5391b6a0..1833d7cd 100644 --- a/front/src/Administration/ConsoleGlobalMessageManager.ts +++ b/front/src/Administration/ConsoleGlobalMessageManager.ts @@ -332,7 +332,7 @@ export class ConsoleGlobalMessageManager { } active(){ - this.userInputManager.clearAllInputKeyboard(); + this.userInputManager.clearAllKeys(); this.divMainConsole.style.top = '0'; this.activeConsole = true; } diff --git a/front/src/Administration/UserMessageManager.ts b/front/src/Administration/UserMessageManager.ts index 12022b03..a20b4729 100644 --- a/front/src/Administration/UserMessageManager.ts +++ b/front/src/Administration/UserMessageManager.ts @@ -1,5 +1,8 @@ import {RoomConnection} from "../Connexion/RoomConnection"; import * as TypeMessages from "./TypeMessage"; +import List = Phaser.Structs.List; +import {UpdatedLocalStreamCallback} from "../WebRtc/MediaManager"; +import {Banned} from "./TypeMessage"; export interface TypeMessageInterface { showMessage(message: string): void; @@ -8,6 +11,7 @@ export interface TypeMessageInterface { export class UserMessageManager { typeMessages: Map = new Map(); + receiveBannedMessageListener: Set = new Set(); constructor(private Connection: RoomConnection) { const valueTypeMessageTab = Object.values(TypeMessages); @@ -21,7 +25,14 @@ export class UserMessageManager { initialise() { //receive signal to show message this.Connection.receiveUserMessage((type: string, message: string) => { - this.showMessage(type, message); + const typeMessage = this.showMessage(type, message); + + //listener on banned receive message + if(typeMessage instanceof Banned) { + for (const callback of this.receiveBannedMessageListener) { + callback(); + } + } }); } @@ -32,5 +43,10 @@ export class UserMessageManager { return; } classTypeMessage.showMessage(message); + return classTypeMessage; + } + + setReceiveBanListener(callback: Function){ + this.receiveBannedMessageListener.add(callback); } } \ No newline at end of file diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 2e6451f3..a0ae3119 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -96,7 +96,9 @@ export interface WebRtcSignalSentMessageInterface { export interface WebRtcSignalReceivedMessageInterface { userId: number, - signal: SignalData + signal: SignalData, + webRtcUser: string | undefined, + webRtcPassword: string | undefined } export interface StartMapInterface { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index bd330ad9..cebd7606 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -427,7 +427,9 @@ export class RoomConnection implements RoomConnection { callback({ userId: message.getUserid(), name: message.getName(), - initiator: message.getInitiator() + initiator: message.getInitiator(), + webRtcUser: message.getWebrtcusername() ?? undefined, + webRtcPassword: message.getWebrtcpassword() ?? undefined, }); }); } @@ -436,7 +438,9 @@ export class RoomConnection implements RoomConnection { this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => { callback({ userId: message.getUserid(), - signal: JSON.parse(message.getSignal()) + signal: JSON.parse(message.getSignal()), + webRtcUser: message.getWebrtcusername() ?? undefined, + webRtcPassword: message.getWebrtcpassword() ?? undefined, }); }); } @@ -445,7 +449,9 @@ export class RoomConnection implements RoomConnection { this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => { callback({ userId: message.getUserid(), - signal: JSON.parse(message.getSignal()) + signal: JSON.parse(message.getSignal()), + webRtcUser: message.getWebrtcusername() ?? undefined, + webRtcPassword: message.getWebrtcpassword() ?? undefined, }); }); } @@ -464,7 +470,8 @@ export class RoomConnection implements RoomConnection { }); } - public getUserId(): number|null { + public getUserId(): number { + if (this.userId === null) throw 'UserId cannot be null!' return this.userId; } @@ -583,7 +590,7 @@ export class RoomConnection implements RoomConnection { public hasTag(tag: string): boolean { return this.tags.includes(tag); } - + public isAdmin(): boolean { return this.hasTag('admin'); } diff --git a/front/src/Cypress/CypressAsserter.ts b/front/src/Cypress/CypressAsserter.ts deleted file mode 100644 index 82eeab1f..00000000 --- a/front/src/Cypress/CypressAsserter.ts +++ /dev/null @@ -1,36 +0,0 @@ -declare let window:WindowWithCypressAsserter; - -interface WindowWithCypressAsserter extends Window { - cypressAsserter: CypressAsserter; -} - -//this class is used to communicate with cypress, our e2e testing client -//Since cypress cannot manipulate canvas, we notified it with console logs -class CypressAsserter { - - constructor() { - window.cypressAsserter = this - } - - gameStarted() { - console.log('Started the game') - } - - preloadStarted() { - console.log('Preloading') - } - - preloadFinished() { - console.log('Preloading done') - } - - initStarted() { - console.log('startInit') - } - - initFinished() { - console.log('startInit done') - } -} - -export const cypressAsserter = new CypressAsserter() diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index a71506eb..844bf564 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -3,9 +3,10 @@ const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.wo 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"); -const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca"; -const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com'; -const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$'; +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 || ''; +const TURN_PASSWORD: string = process.env.TURN_PASSWORD || ''; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true"; const RESOLUTION = 2; @@ -23,6 +24,7 @@ export { ZOOM_LEVEL, POSITION_DELAY, MAX_EXTRAPOLATION_TIME, + STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD, diff --git a/front/src/Phaser/Components/Loader.ts b/front/src/Phaser/Components/Loader.ts index ab9c0d95..1ee18b32 100644 --- a/front/src/Phaser/Components/Loader.ts +++ b/front/src/Phaser/Components/Loader.ts @@ -1,14 +1,54 @@ +import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig; -export const addLoader = (scene:Phaser.Scene): void => { - const loadingText = scene.add.text(scene.game.renderer.width / 2, 200, 'Loading'); +const LogoNameIndex: string = 'logoLoading'; +const TextName: string = 'Loading...'; +const LogoResource: string = 'resources/logos/logo.png'; +const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59}; + +export const addLoader = (scene: Phaser.Scene): void => { + // If there is nothing to load, do not display the loader. + if (scene.load.list.entries.length === 0) { + return; + } + let loadingText: Phaser.GameObjects.Text|null = null; + const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3); + const loadingBarHeight: number = 16; + const padding: number = 5; + + const promiseLoadLogoTexture = new Promise((res) => { + if(scene.load.textureManager.exists(LogoNameIndex)){ + return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)); + }else{ + //add loading if logo image is not ready + loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName); + } + scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame); + scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => { + if(loadingText){ + loadingText.destroy(); + } + return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)); + }); + }); + + const progressContainer = scene.add.graphics(); const progress = scene.add.graphics(); + progressContainer.fillStyle(0x444444, 0.8); + progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2); + scene.load.on('progress', (value: number) => { progress.clear(); - progress.fillStyle(0xffffff, 1); - progress.fillRect(0, 270, 800 * value, 60); + progress.fillStyle(0xBBBBBB, 1); + progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight); }); scene.load.on('complete', () => { - loadingText.destroy(); + if(loadingText){ + loadingText.destroy(); + } + promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => { + resLoadingImage.destroy(); + }); progress.destroy(); + progressContainer.destroy(); }); -} \ No newline at end of file +} diff --git a/front/src/Phaser/Components/OpenChatIcon.ts b/front/src/Phaser/Components/OpenChatIcon.ts index bf293bab..1e9429e8 100644 --- a/front/src/Phaser/Components/OpenChatIcon.ts +++ b/front/src/Phaser/Components/OpenChatIcon.ts @@ -3,14 +3,12 @@ import {discussionManager} from "../../WebRtc/DiscussionManager"; export const openChatIconName = 'openChatIcon'; export class OpenChatIcon extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number) { - super(scene, x, y, openChatIconName); + super(scene, x, y, openChatIconName, 3); scene.add.existing(this); this.setScrollFactor(0, 0); this.setOrigin(0, 1); - this.displayWidth = 30; - this.displayHeight = 30; this.setInteractive(); - this.setVisible(false) + this.setVisible(false); this.setDepth(99999); this.on("pointerup", () => discussionManager.showDiscussionPart()); diff --git a/front/src/Phaser/Entity/PlayerTextures.ts b/front/src/Phaser/Entity/PlayerTextures.ts index 7d10cc1a..d0542d6a 100644 --- a/front/src/Phaser/Entity/PlayerTextures.ts +++ b/front/src/Phaser/Entity/PlayerTextures.ts @@ -6,7 +6,8 @@ export interface BodyResourceDescriptionListInterface { export interface BodyResourceDescriptionInterface { name: string, - img: string + img: string, + level?: number } export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = { diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 776d1f5c..78b66c10 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -23,21 +23,26 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio }); return returnArray; } -export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => { + +export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise => { const name = 'customCharacterTexture'+texture.id; - load.spritesheet(name,texture.url,{frameWidth: 32, frameHeight: 32}); - return name; + const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level} + return createLoadingPromise(loaderPlugin, playerResourceDescriptor); } -export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturePlugin: TextureManager, texturekeys:Array): Promise => { - const promisesList:Promise[] = []; +export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array): Promise => { + const promisesList:Promise[] = []; texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => { - const playerResourceDescriptor = getRessourceDescriptor(textureKey); - if(!texturePlugin.exists(playerResourceDescriptor.name)) { - console.log('Loading '+playerResourceDescriptor.name) - promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor)); + try { + //TODO refactor + const playerResourceDescriptor = getRessourceDescriptor(textureKey); + if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { + promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor)); + } + }catch (err){ + console.error(err); } - }) + }); let returnPromise:Promise>; if (promisesList.length > 0) { loadPlugin.start(); @@ -66,8 +71,14 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio } const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => { - return new Promise((res, rej) => { - loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {frameWidth: 32, frameHeight: 32}); - loadPlugin.once('filecomplete-spritesheet-'+playerResourceDescriptor.name, () => res()); + return new Promise((res) => { + if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { + return res(playerResourceDescriptor); + } + loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, { + frameWidth: 32, + frameHeight: 32 + }); + loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor)); }); } \ No newline at end of file diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 822e4e36..81ea00e1 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -1,7 +1,6 @@ import {GameScene} from "../Game/GameScene"; import {PointInterface} from "../../Connexion/ConnexionModels"; import {Character} from "../Entity/Character"; -import {Sprite} from "./Sprite"; /** * Class representing the sprite of a remote player (a player that plays on another computer) @@ -23,6 +22,11 @@ export class RemotePlayer extends Character { //set data this.userId = userId; + + //todo: implement on click action + /*this.playerName.setInteractive(); + this.playerName.on('pointerup', () => { + });*/ } updatePosition(position: PointInterface): void { diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index 06a64bd4..231de875 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -14,7 +14,7 @@ export class SpeechBubble { const bubbleWidth = bubblePadding * 2 + text.length * 10; const arrowHeight = bubbleHeight / 4; - this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 }); + this.bubble = scene.add.graphics({ x: 16, y: -80 }); player.add(this.bubble); // Bubble shadow diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 9f3157a0..12da0514 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -49,6 +49,10 @@ export class GameMap { this.lastProperties = newProps; } + public getCurrentProperties(): Map { + return this.lastProperties; + } + private getProperties(key: number): Map { const properties = new Map(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 51126013..d6dc7dbb 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -30,10 +30,11 @@ import {RemotePlayer} from "../Entity/RemotePlayer"; import {Queue} from 'queue-typescript'; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; -import {lazyLoadPlayerCharacterTextures} from "../Entity/PlayerTexturesLoadingManager"; +import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; import { CenterListener, - EXIT_MESSAGE_PROPERTIES, JITSI_MESSAGE_PROPERTIES, + EXIT_MESSAGE_PROPERTIES, + JITSI_MESSAGE_PROPERTIES, layoutManager, LayoutMode, ON_ACTION_TRIGGER_BUTTON, @@ -72,6 +73,8 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha import {TextureError} from "../../Exception/TextureError"; import {addLoader} from "../Components/Loader"; import {ErrorSceneName} from "../Reconnecting/ErrorScene"; +import {localUserStore} from "../../Connexion/LocalUserStore"; +import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -116,7 +119,7 @@ export class GameScene extends ResizableScene implements CenterListener { MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayersByKey : Map = new Map(); Map!: Phaser.Tilemaps.Tilemap; - Layers!: Array; + Layers!: Array; Objects!: Array; mapFile!: ITiledMap; groups: Map; @@ -157,7 +160,7 @@ export class GameScene extends ResizableScene implements CenterListener { private actionableItems: Map = new Map(); // The item that can be selected by pressing the space key. private outlinedItem: ActionableItem|null = null; - private userInputManager!: UserInputManager; + public userInputManager!: UserInputManager; private isReconnecting: boolean = false; private startLayerName!: string | null; private openChatIcon!: OpenChatIcon; @@ -186,7 +189,13 @@ export class GameScene extends ResizableScene implements CenterListener { //hook preload scene preload(): void { - addLoader(this); + const localUser = localUserStore.getLocalUser(); + const textures = localUser?.textures; + if (textures) { + for (const texture of textures) { + loadCustomTexture(this.load, texture); + } + } this.load.image(openChatIconName, 'resources/objects/talk.png'); this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { @@ -210,6 +219,8 @@ export class GameScene extends ResizableScene implements CenterListener { this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); + + addLoader(this); } // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. @@ -344,11 +355,11 @@ export class GameScene extends ResizableScene implements CenterListener { this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels); //add layer on map - this.Layers = new Array(); + this.Layers = new Array(); let depth = -2; for (const layer of this.mapFile.layers) { if (layer.type === 'tilelayer') { - this.addLayer(this.Map.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); + this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); const exitSceneUrl = this.getExitSceneUrl(layer); if (exitSceneUrl !== undefined) { @@ -529,6 +540,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName); this.GlobalMessageManager = new GlobalMessageManager(this.connection); this.UserMessageManager = new UserMessageManager(this.connection); + this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); const self = this; this.simplePeer.registerPeerConnectionListener({ @@ -625,6 +637,15 @@ export class GameScene extends ResizableScene implements CenterListener { } } + private safeParseJSONstring(jsonString: string|undefined, propertyName: string) { + try { + return jsonString ? JSON.parse(jsonString) : {}; + } catch(e) { + console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e); + return {} + } + } + private triggerOnMapLayerPropertyChange(){ /* @deprecated this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => { @@ -656,7 +677,7 @@ export class GameScene extends ResizableScene implements CenterListener { coWebsiteManager.closeCoWebsite(); }else{ const openWebsiteFunction = () => { - coWebsiteManager.loadCoWebsite(newValue as string); + coWebsiteManager.loadCoWebsite(newValue as string, allProps.get('openWebsitePolicy') as string | undefined); layoutManager.removeActionButton('openWebsite', this.userInputManager); }; @@ -680,11 +701,13 @@ export class GameScene extends ResizableScene implements CenterListener { this.stopJitsi(); }else{ const openJitsiRoomFunction = () => { + const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance); if (JITSI_PRIVATE_MODE) { const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; - this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag); + + this.connection.emitQueryJitsiJwtMessage(roomName, adminTag); } else { - this.startJitsi(newValue as string); + this.startJitsi(roomName, undefined); } layoutManager.removeActionButton('jitsiRoom', this.userInputManager); } @@ -725,6 +748,10 @@ export class GameScene extends ResizableScene implements CenterListener { if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey); urlManager.pushStartLayerNameToUrl(hash); if (roomId !== this.scene.key) { + if (this.scene.get(roomId) === null) { + console.error("next room not loaded", exitKey); + return; + } this.cleanupClosingScene(); this.scene.stop(); this.scene.remove(this.scene.key); @@ -878,13 +905,13 @@ export class GameScene extends ResizableScene implements CenterListener { this.cameras.main.setZoom(ZOOM_LEVEL); } - addLayer(Layer : Phaser.Tilemaps.TilemapLayer){ + addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ this.Layers.push(Layer); } createCollisionWithPlayer() { //add collision layer - this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => { + this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => { //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); @@ -902,7 +929,7 @@ export class GameScene extends ResizableScene implements CenterListener { createCurrentPlayer(){ //TODO create animation moving between exit and start - const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, this.characterLayers); + const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); try { this.CurrentPlayer = new Player( this, @@ -1096,7 +1123,7 @@ export class GameScene extends ResizableScene implements CenterListener { return; } - const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, addPlayerData.characterLayers); + const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers); const player = new RemotePlayer( addPlayerData.userId, this, @@ -1221,6 +1248,7 @@ export class GameScene extends ResizableScene implements CenterListener { private reposition(): void { this.presentationModeSprite.setY(this.game.renderer.height - 2); this.chatModeSprite.setY(this.game.renderer.height - 2); + this.openChatIcon.setY(this.game.renderer.height - 2); // Recompute camera offset if needed this.updateCameraOffset(); @@ -1247,7 +1275,11 @@ export class GameScene extends ResizableScene implements CenterListener { } public startJitsi(roomName: string, jwt?: string): void { - jitsiFactory.start(roomName, this.playerName, jwt); + const allProps = this.gameMap.getCurrentProperties(); + const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig'); + const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig'); + + jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig); this.connection.setSilent(true); mediaManager.hideGameOverlay(); @@ -1265,5 +1297,14 @@ export class GameScene extends ResizableScene implements CenterListener { mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); } + private bannedUser(){ + this.cleanupClosingScene(); + this.userInputManager.clearAllKeys(); + this.scene.start(ErrorSceneName, { + title: 'Banned', + subTitle: 'You was banned of WorkAdventure', + message: 'If you want more information, you can contact us: workadventure@thecodingmachine.com' + }); + } } diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index e32bad1a..fe4de385 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -43,8 +43,9 @@ export class ActionableItem { } this.isSelectable = true; if (this.sprite.pipeline) { - this.sprite.setPipeline(OutlinePipeline.KEY); - this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height); + // Commented out to try to fix MacOS issue + /*this.sprite.setPipeline(OutlinePipeline.KEY); + this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/ } } @@ -56,7 +57,8 @@ export class ActionableItem { return; } this.isSelectable = false; - this.sprite.resetPipeline(); + // Commented out to try to fix MacOS issue + //this.sprite.resetPipeline(); } /** diff --git a/front/src/Phaser/Login/AbstractCharacterScene.ts b/front/src/Phaser/Login/AbstractCharacterScene.ts new file mode 100644 index 00000000..dfc98539 --- /dev/null +++ b/front/src/Phaser/Login/AbstractCharacterScene.ts @@ -0,0 +1,41 @@ +import {ResizableScene} from "./ResizableScene"; +import {localUserStore} from "../../Connexion/LocalUserStore"; +import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; +import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; +import {CharacterTexture} from "../../Connexion/LocalUser"; + +export abstract class AbstractCharacterScene extends ResizableScene { + + loadCustomSceneSelectCharacters() : Promise { + const textures = this.getTextures(); + const promises : Promise[] = []; + if (textures) { + for (const texture of textures) { + if (texture.level === -1) { + continue; + } + promises.push(loadCustomTexture(this.load, texture)); + } + } + return Promise.all(promises) + } + + loadSelectSceneCharacters() : Promise { + const textures = this.getTextures(); + const promises: Promise[] = []; + if (textures) { + for (const texture of textures) { + if (texture.level !== -1) { + continue; + } + promises.push(loadCustomTexture(this.load, texture)); + } + } + return Promise.all(promises) + } + + private getTextures() : CharacterTexture[]|undefined{ + const localUser = localUserStore.getLocalUser(); + return localUser?.textures; + } +} \ No newline at end of file diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index c6364ef6..4f5b2860 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -10,6 +10,7 @@ import {ResizableScene} from "./ResizableScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; +import {AbstractCharacterScene} from "./AbstractCharacterScene"; export const CustomizeSceneName = "CustomizeScene"; @@ -20,7 +21,7 @@ enum CustomizeTextures{ arrowUp = "arrow_up", } -export class CustomizeScene extends ResizableScene { +export class CustomizeScene extends AbstractCharacterScene { private textField!: TextField; private enterField!: TextField; @@ -48,29 +49,21 @@ export class CustomizeScene extends ResizableScene { preload() { addLoader(this); + + this.layers = loadAllLayers(this.load); + this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => { + bodyResourceDescriptions.forEach((bodyResourceDescription) => { + if(!bodyResourceDescription.level){ + throw 'Texture level is null'; + } + this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); + }); + }); + this.load.image(CustomizeTextures.arrowRight, "resources/objects/arrow_right.png"); this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png"); this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png"); this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); - - this.layers = loadAllLayers(this.load); - - const localUser = localUserStore.getLocalUser(); - - const textures = localUser?.textures; - if (textures) { - for (const texture of textures) { - if(texture.level === -1){ - continue; - } - loadCustomTexture(this.load, texture); - const name = 'customCharacterTexture'+texture.id; - this.layers[texture.level].unshift({ - name, - img: texture.url - }); - } - } } create() { diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 8d7202a8..9ca6dcd2 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -2,7 +2,6 @@ import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; import {TextInput} from "../Components/TextInput"; import Image = Phaser.GameObjects.Image; -import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; @@ -29,16 +28,13 @@ export class LoginScene extends ResizableScene { } preload() { - cypressAsserter.preloadStarted(); //this.load.image(LoginTextures.playButton, "resources/objects/play_button.png"); this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png"); // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); - cypressAsserter.preloadFinished(); } create() { - cypressAsserter.initStarted(); this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:'); this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => { @@ -59,8 +55,6 @@ export class LoginScene extends ResizableScene { } this.login(this.name); }); - - cypressAsserter.initFinished(); } update(time: number, delta: number): void { diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 25af61c6..e47cf38a 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -9,6 +9,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore"; import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; +import {AbstractCharacterScene} from "./AbstractCharacterScene"; //todo: put this constants in a dedicated file @@ -21,7 +22,7 @@ enum LoginTextures { customizeButtonSelected = "customize_button_selected" } -export class SelectCharacterScene extends ResizableScene { +export class SelectCharacterScene extends AbstractCharacterScene { private readonly nbCharactersPerRow = 6; private textField!: TextField; private pressReturnField!: TextField; @@ -44,6 +45,13 @@ export class SelectCharacterScene extends ResizableScene { preload() { addLoader(this); + + this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => { + bodyResourceDescriptions.forEach((bodyResourceDescription) => { + this.playerModels.push(bodyResourceDescription); + }); + }) + this.load.image(LoginTextures.playButton, "resources/objects/play_button.png"); this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png"); // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap @@ -52,17 +60,7 @@ export class SelectCharacterScene extends ResizableScene { this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png'); this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png'); - const localUser = localUserStore.getLocalUser(); - const textures = localUser?.textures; - if (textures) { - for (const texture of textures) { - if(texture.level !== -1){ - continue; - } - const name = loadCustomTexture(this.load, texture); - this.playerModels.push({name: name, img: texture.url}); - } - } + addLoader(this); } create() { @@ -127,7 +125,7 @@ export class SelectCharacterScene extends ResizableScene { /*create user*/ this.createCurrentPlayer(); - + const playerNumber = localUserStore.getPlayerCharacterIndex(); if (playerNumber && playerNumber !== -1) { this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow; diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 8bd64cd1..ab25c338 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -3,7 +3,9 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha import {gameManager} from "../Game/GameManager"; import {localUserStore} from "../../Connexion/LocalUserStore"; import {mediaManager} from "../../WebRtc/MediaManager"; -import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; +import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu"; +import {connectionManager} from "../../Connexion/ConnectionManager"; +import {GameConnexionTypes} from "../../Url/UrlManager"; export const MenuSceneName = 'MenuScene'; const gameMenuKey = 'gameMenu'; @@ -21,6 +23,7 @@ export class MenuScene extends Phaser.Scene { private menuElement!: Phaser.GameObjects.DOMElement; private gameQualityMenuElement!: Phaser.GameObjects.DOMElement; private gameShareElement!: Phaser.GameObjects.DOMElement; + private gameReportElement!: ReportMenu; private sideMenuOpened = false; private settingsMenuOpened = false; private gameShareOpened = false; @@ -40,20 +43,21 @@ export class MenuScene extends Phaser.Scene { this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html'); this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html'); this.load.html(gameShare, 'resources/html/gameShare.html'); + this.load.html(gameReportKey, gameReportRessource); } create() { this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey); this.menuElement.setOrigin(0); - this.revealMenusAfterInit(this.menuElement, 'gameMenu'); + MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu'); const middleX = (window.innerWidth / 3) - 298; this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey); - this.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality'); + MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality'); this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare); - this.revealMenusAfterInit(this.gameShareElement, gameShare); + MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare); this.gameShareElement.addListener('click'); this.gameShareElement.on('click', (event:MouseEvent) => { event.preventDefault(); @@ -64,6 +68,12 @@ export class MenuScene extends Phaser.Scene { } }); + this.gameReportElement = new ReportMenu(this, connectionManager.getConnexionType === GameConnexionTypes.anonymous); + mediaManager.setShowReportModalCallBacks((userId, userName) => { + this.closeAll(); + this.gameReportElement.open(parseInt(userId), userName); + }); + this.input.keyboard.on('keyup-TAB', () => { this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu(); }); @@ -77,7 +87,8 @@ export class MenuScene extends Phaser.Scene { this.menuElement.on('click', this.onMenuClick.bind(this)); } - private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) { + //todo put this method in a parent menuElement class + static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) { //Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect. //To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done. setTimeout(() => { @@ -98,6 +109,11 @@ export class MenuScene extends Phaser.Scene { const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement; adminSection.hidden = false; } + //TODO bind with future metadata of card + //if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){ + const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement; + adminSection.hidden = false; + //} this.tweens.add({ targets: this.menuElement, x: openedSideMenuX, @@ -222,6 +238,9 @@ export class MenuScene extends Phaser.Scene { } private onMenuClick(event:MouseEvent) { + if((event?.target as HTMLInputElement).classList.contains('not-button')){ + return; + } event.preventDefault(); switch ((event?.target as HTMLInputElement).id) { @@ -280,5 +299,6 @@ export class MenuScene extends Phaser.Scene { private closeAll(){ this.closeGameQualityMenu(); this.closeGameShare(); + this.gameReportElement.close(); } } diff --git a/front/src/Phaser/Menu/ReportMenu.ts b/front/src/Phaser/Menu/ReportMenu.ts new file mode 100644 index 00000000..bee86c35 --- /dev/null +++ b/front/src/Phaser/Menu/ReportMenu.ts @@ -0,0 +1,119 @@ +import {MenuScene} from "./MenuScene"; +import {gameManager} from "../Game/GameManager"; +import {blackListManager} from "../../WebRtc/BlackListManager"; + +export const gameReportKey = 'gameReport'; +export const gameReportRessource = 'resources/html/gameReport.html'; + +export class ReportMenu extends Phaser.GameObjects.DOMElement { + private opened: boolean = false; + + private userId!: number; + private userName!: string|undefined; + private anonymous: boolean; + + constructor(scene: Phaser.Scene, anonymous: boolean) { + super(scene, -2000, -2000); + this.anonymous = anonymous; + this.createFromCache(gameReportKey); + + if (this.anonymous) { + const divToHide = this.getChildByID('reportSection') as HTMLElement; + divToHide.hidden = true; + const textToHide = this.getChildByID('askActionP') as HTMLElement; + textToHide.hidden = true; + } + + scene.add.existing(this); + MenuScene.revealMenusAfterInit(this, gameReportKey); + + this.addListener('click'); + this.on('click', (event:MouseEvent) => { + event.preventDefault(); + if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') { + this.submitReport(); + } else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') { + this.close(); + } else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') { + this.toggleBlock(); + } + }); + } + + public open(userId: number, userName: string|undefined): void { + if (this.opened) { + this.close(); + return; + } + + this.userId = userId; + this.userName = userName; + + const mainEl = this.getChildByID('gameReport') as HTMLElement; + this.x = this.getCenteredX(mainEl); + this.y = this.getHiddenY(mainEl); + + const gameTitleReport = this.getChildByID('nameReported') as HTMLElement; + gameTitleReport.innerText = userName || ''; + + const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement; + blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user'; + + this.opened = true; + + gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys(); + + this.scene.tweens.add({ + targets: this, + y: this.getCenteredY(mainEl), + duration: 1000, + ease: 'Power3' + }); + } + + public close(): void { + this.opened = false; + gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent(); + const mainEl = this.getChildByID('gameReport') as HTMLElement; + this.scene.tweens.add({ + targets: this, + y: this.getHiddenY(mainEl), + duration: 1000, + ease: 'Power3' + }); + } + + //todo: into a parent class? + private getCenteredX(mainEl: HTMLElement): number { + return window.innerWidth / 4 - mainEl.clientWidth / 2; + } + private getHiddenY(mainEl: HTMLElement): number { + return - mainEl.clientHeight - 50; + } + private getCenteredY(mainEl: HTMLElement): number { + return window.innerHeight / 4 - mainEl.clientHeight / 2; + } + + private toggleBlock(): void { + !blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId); + this.close(); + } + + private submitReport(): void{ + const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement; + gamePError.innerText = ''; + gamePError.style.display = 'none'; + const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement; + const gameIdUserReported = this.getChildByID('idUserReported') as HTMLInputElement; + if(!gameTextArea || !gameTextArea.value || !gameIdUserReported || !gameIdUserReported.value){ + gamePError.innerText = 'Report message cannot to be empty.'; + gamePError.style.display = 'block'; + return; + } + gameManager.getCurrentGameScene(this.scene).connection.emitReportPlayerMessage( + parseInt(gameIdUserReported.value), + gameTextArea.value + ); + this.close(); + } +} \ No newline at end of file diff --git a/front/src/Phaser/Shaders/OutlinePipeline.ts b/front/src/Phaser/Shaders/OutlinePipeline.ts index 0d074bc3..9c123c48 100644 --- a/front/src/Phaser/Shaders/OutlinePipeline.ts +++ b/front/src/Phaser/Shaders/OutlinePipeline.ts @@ -1,4 +1,4 @@ -export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline { +export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline { // the unique id of this pipeline public static readonly KEY = 'Outline'; diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index c8d91609..dd46a343 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -59,7 +59,11 @@ export class UserInputManager { ]; } - clearAllInputKeyboard(){ + clearAllListeners(){ + this.Scene.input.keyboard.removeAllListeners(); + } + + clearAllKeys(){ this.Scene.input.keyboard.removeAllKeys(); } diff --git a/front/src/WebRtc/BlackListManager.ts b/front/src/WebRtc/BlackListManager.ts new file mode 100644 index 00000000..65efef3a --- /dev/null +++ b/front/src/WebRtc/BlackListManager.ts @@ -0,0 +1,24 @@ +import {Subject} from 'rxjs'; + +class BlackListManager { + private list: number[] = []; + public onBlockStream: Subject = new Subject(); + public onUnBlockStream: Subject = new Subject(); + + isBlackListed(userId: number): boolean { + return this.list.find((data) => data === userId) !== undefined; + } + + blackList(userId: number): void { + if (this.isBlackListed(userId)) return; + this.list.push(userId); + this.onBlockStream.next(userId); + } + + cancelBlackList(userId: number): void { + this.list.splice(this.list.findIndex(data => data === userId), 1); + this.onUnBlockStream.next(userId); + } +} + +export const blackListManager = new BlackListManager(); \ No newline at end of file diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index e9564222..ef73ac1d 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -42,7 +42,7 @@ class CoWebsiteManager { this.opened = iframeStates.opened; } - public loadCoWebsite(url: string): void { + public loadCoWebsite(url: string, allowPolicy?: string): void { this.load(); this.cowebsiteDiv.innerHTML = ` + + `; layoutManager.add(DivImportance.Normal, userId, html); - - if (reportCallBack) { - const reportBtn = HtmlUtils.getElementByIdOrFail(`report-${userId}`); - reportBtn.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - this.showReportModal(userId, userName, reportCallBack); - }); - } - + this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); //permit to create participant in discussion part - this.addNewParticipant(userId, userName, undefined, reportCallBack); + const showReportUser = () => { + for(const callBack of this.showReportModalCallBacks){ + callBack(userId, userName); + } + }; + this.addNewParticipant(userId, userName, undefined, showReportUser); + + const reportBanUserActionEl: HTMLImageElement = HtmlUtils.getElementByIdOrFail(`report-${userId}`); + reportBanUserActionEl.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + showReportUser(); + }); } addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ - userId = `screen-sharing-${userId}`; + userId = this.getScreenSharingId(userId); const html = `
@@ -517,7 +526,11 @@ export class MediaManager { this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); } - + + private getScreenSharingId(userId: string): string { + return `screen-sharing-${userId}`; + } + disabledMicrophoneByUserId(userId: number){ const element = document.getElementById(`microphone-${userId}`); if(!element){ @@ -556,6 +569,10 @@ export class MediaManager { } } + toggleBlockLogo(userId: number, show: boolean): void { + const blockLogoElement = HtmlUtils.getElementByIdOrFail('blocking-'+userId); + show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active'); + } addStreamRemoteVideo(userId: string, stream : MediaStream): void { const remoteVideo = this.remoteVideo.get(userId); if (remoteVideo === undefined) { @@ -565,12 +582,12 @@ export class MediaManager { } addStreamRemoteScreenSharing(userId: string, stream : MediaStream){ // In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet - const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`); + const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId)); if (remoteVideo === undefined) { this.addScreenSharingActiveVideo(userId); } - this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream); + this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream); } removeActiveVideo(userId: string){ @@ -581,7 +598,7 @@ export class MediaManager { this.removeParticipant(userId); } removeActiveScreenSharingVideo(userId: string) { - this.removeActiveVideo(`screen-sharing-${userId}`) + this.removeActiveVideo(this.getScreenSharingId(userId)) } playWebrtcOutSound(): void { @@ -617,7 +634,7 @@ export class MediaManager { errorDiv.style.display = 'block'; } isErrorScreenSharing(userId: string): void { - this.isError(`screen-sharing-${userId}`); + this.isError(this.getScreenSharingId(userId)); } @@ -645,65 +662,8 @@ export class MediaManager { return color; } - public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ - //create report text area - const mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); - - const divReport = document.createElement('div'); - divReport.classList.add('modal-report-user'); - - const inputHidden = document.createElement('input'); - inputHidden.id = 'input-report-user'; - inputHidden.type = 'hidden'; - inputHidden.value = userId; - divReport.appendChild(inputHidden); - - const titleMessage = document.createElement('p'); - titleMessage.id = 'title-report-user'; - titleMessage.innerText = 'Open a report'; - divReport.appendChild(titleMessage); - - const bodyMessage = document.createElement('p'); - bodyMessage.id = 'body-report-user'; - bodyMessage.innerText = `You are about to open a report regarding an offensive conduct from user ${userName.toUpperCase()}. Please explain to us how you think ${userName.toUpperCase()} breached the code of conduct.`; - divReport.appendChild(bodyMessage); - - const imgReportUser = document.createElement('img'); - imgReportUser.id = 'img-report-user'; - imgReportUser.src = 'resources/logos/report.svg'; - divReport.appendChild(imgReportUser); - - const textareaUser = document.createElement('textarea'); - textareaUser.id = 'textarea-report-user'; - textareaUser.placeholder = 'Write ...'; - divReport.appendChild(textareaUser); - - const buttonReport = document.createElement('button'); - buttonReport.id = 'button-save-report-user'; - buttonReport.innerText = 'Report'; - buttonReport.addEventListener('click', () => { - if(!textareaUser.value){ - textareaUser.style.border = '1px solid red' - return; - } - reportCallBack(textareaUser.value); - divReport.remove(); - }); - divReport.appendChild(buttonReport); - - const buttonCancel = document.createElement('img'); - buttonCancel.id = 'cancel-report-user'; - buttonCancel.src = 'resources/logos/close.svg'; - buttonCancel.addEventListener('click', () => { - divReport.remove(); - }); - divReport.appendChild(buttonCancel); - - mainContainer.appendChild(divReport); - } - - public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){ - discussionManager.addParticipant(userId, name, img, false, reportCallBack); + public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){ + discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack); } public removeParticipant(userId: number|string){ @@ -769,6 +729,10 @@ export class MediaManager { this.checkActiveUser(); }, this.focused ? 10000 : 1000); } + + public setShowReportModalCallBacks(callback: ShowReportCallBack){ + this.showReportModalCallBacks.add(callback); + } } export const mediaManager = new MediaManager(); diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index a6cf679b..62f3079c 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -1,8 +1,9 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; -import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable"; +import {STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "../Connexion/RoomConnection"; import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer"; +import {UserSimplePeerInterface} from "./SimplePeer"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -16,25 +17,28 @@ export class ScreenSharingPeer extends Peer { private isReceivingStream:boolean = false; public toClose: boolean = false; public _connected: boolean = false; + private userId: number; - constructor(private userId: number, initiator: boolean, private connection: RoomConnection) { + constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { super({ initiator: initiator ? initiator : false, reconnectTimer: 10000, config: { iceServers: [ { - urls: 'stun:stun.l.google.com:19302' + urls: STUN_SERVER.split(',') }, - { + TURN_SERVER !== '' ? { urls: TURN_SERVER.split(','), - username: TURN_USER, - credential: TURN_PASSWORD - }, - ] + username: user.webRtcUser || TURN_USER, + credential: user.webRtcPassword || TURN_PASSWORD + } : undefined, + ].filter((value) => value !== undefined) } }); + this.userId = user.userId; + //start listen signal for the peer connection this.on('signal', (data: unknown) => { this.sendWebrtcScreenSharingSignal(data); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 90d260ee..34e4b3f8 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -9,13 +9,18 @@ import { UpdatedLocalStreamCallback } from "./MediaManager"; import {ScreenSharingPeer} from "./ScreenSharingPeer"; -import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer"; +import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer"; import {RoomConnection} from "../Connexion/RoomConnection"; +import {connectionManager} from "../Connexion/ConnectionManager"; +import {GameConnexionTypes} from "../Url/UrlManager"; +import {blackListManager} from "./BlackListManager"; export interface UserSimplePeerInterface{ userId: number; name?: string; initiator?: boolean; + webRtcUser?: string|undefined; + webRtcPassword?: string|undefined; } export interface PeerConnectionListener { @@ -36,6 +41,7 @@ export class SimplePeer { private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly peerConnectionListeners: Array = new Array(); + private readonly userId: number; constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) { // We need to go through this weird bound function pointer in order to be able to "free" this reference later. @@ -46,6 +52,7 @@ export class SimplePeer { mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback); mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback); + this.userId = Connection.getUserId(); this.initialise(); } @@ -89,15 +96,14 @@ export class SimplePeer { }); } - private receiveWebrtcStart(user: UserSimplePeerInterface) { - //this.WebRtcRoomId = data.roomId; + private receiveWebrtcStart(user: UserSimplePeerInterface): void { this.Users.push(user); // Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group) // So we can receive a request we already had before. (which will abort at the first line of createPeerConnection) // This would be symmetrical to the way we handle disconnection. - + //start connection - console.log('receiveWebrtcStart. Initiator: ', user.initiator) + //console.log('receiveWebrtcStart. Initiator: ', user.initiator) if(!user.initiator){ return; } @@ -134,17 +140,13 @@ export class SimplePeer { mediaManager.removeActiveVideo("" + user.userId); - const reportCallback = this.enableReporting ? (comment: string) => { - this.reportUser(user.userId, comment); - } : undefined; + mediaManager.addActiveVideo(user, name); - mediaManager.addActiveVideo("" + user.userId, reportCallback, name); - - const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection); //permit to send message mediaManager.addSendMessageCallback(user.userId,(message: string) => { - peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message}))); + peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), userId: this.userId, message: message}))); }); peer.toClose = false; @@ -189,7 +191,7 @@ export class SimplePeer { mediaManager.addScreenSharingActiveVideo("" + user.userId); } - const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection); this.PeerScreenSharingConnectionArray.set(user.userId, peer); for (const peerConnectionListener of this.peerConnectionListeners) { @@ -300,6 +302,7 @@ export class SimplePeer { } private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) { + if (blackListManager.isBlackListed(data.userId)) return; console.log("receiveWebrtcScreenSharingSignal", data); try { //if offer type, create peer connection @@ -391,14 +394,8 @@ export class SimplePeer { } } - /** - * Triggered locally when clicking on the report button - */ - public reportUser(userId: number, message: string) { - this.Connection.emitReportPlayerMessage(userId, message) - } - private sendLocalScreenSharingStreamToUser(userId: number): void { + if (blackListManager.isBlackListed(userId)) return; // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) if (this.PeerScreenSharingConnectionArray.has(userId)) { this.pushScreenSharingToRemoteUser(userId); diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 877f4e64..416a17c9 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -1,65 +1,57 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; -import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable"; +import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "../Connexion/RoomConnection"; +import {blackListManager} from "./BlackListManager"; +import {Subscription} from "rxjs"; +import {UserSimplePeerInterface} from "./SimplePeer"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); export const MESSAGE_TYPE_CONSTRAINT = 'constraint'; export const MESSAGE_TYPE_MESSAGE = 'message'; +export const MESSAGE_TYPE_BLOCKED = 'blocked'; +export const MESSAGE_TYPE_UNBLOCKED = 'unblocked'; /** * A peer connection used to transmit video / audio signals between 2 peers. */ export class VideoPeer extends Peer { public toClose: boolean = false; public _connected: boolean = false; + private remoteStream!: MediaStream; + private blocked: boolean = false; + private userId: number; + private userName: string; + private onBlockSubscribe: Subscription; + private onUnBlockSubscribe: Subscription; - constructor(public userId: number, initiator: boolean, private connection: RoomConnection) { + constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { super({ initiator: initiator ? initiator : false, reconnectTimer: 10000, config: { iceServers: [ { - urls: 'stun:stun.l.google.com:19302' + urls: STUN_SERVER.split(',') }, - { + TURN_SERVER !== '' ? { urls: TURN_SERVER.split(','), - username: TURN_USER, - credential: TURN_PASSWORD - }, - ] + username: user.webRtcUser || TURN_USER, + credential: user.webRtcPassword || TURN_PASSWORD + } : undefined, + ].filter((value) => value !== undefined) } }); - console.log('PEER SETUP ', { - initiator: initiator ? initiator : false, - reconnectTimer: 10000, - config: { - iceServers: [ - { - urls: 'stun:stun.l.google.com:19302' - }, - { - urls: TURN_SERVER.split(','), - username: TURN_USER, - credential: TURN_PASSWORD - }, - ] - } - }); + this.userId = user.userId; + this.userName = user.name || ''; //start listen signal for the peer connection this.on('signal', (data: unknown) => { this.sendWebrtcSignal(data); }); - this.on('stream', (stream: MediaStream) => { - this.stream(stream); - }); - - /*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => { - });*/ + this.on('stream', (stream: MediaStream) => this.stream(stream)); this.on('close', () => { this._connected = false; @@ -70,7 +62,7 @@ export class VideoPeer extends Peer { // eslint-disable-next-line @typescript-eslint/no-explicit-any this.on('error', (err: any) => { console.error(`error => ${this.userId} => ${err.code}`, err); - mediaManager.isError("" + userId); + mediaManager.isError("" + this.userId); }); this.on('connect', () => { @@ -81,8 +73,6 @@ export class VideoPeer extends Peer { this.on('data', (chunk: Buffer) => { const message = JSON.parse(chunk.toString('utf8')); - console.log("data", message); - if(message.type === MESSAGE_TYPE_CONSTRAINT) { if (message.audio) { mediaManager.enabledMicrophoneByUserId(this.userId); @@ -95,8 +85,19 @@ export class VideoPeer extends Peer { } else { mediaManager.disabledVideoByUserId(this.userId); } - } else if(message.type === 'message') { - mediaManager.addNewMessage(message.name, message.message); + } else if(message.type === MESSAGE_TYPE_MESSAGE) { + if (!blackListManager.isBlackListed(message.userId)) { + mediaManager.addNewMessage(message.name, message.message); + } + } else if(message.type === MESSAGE_TYPE_BLOCKED) { + //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream. + // Find a way to block A's output stream in A's js client + //However, the output stream stream B is correctly blocked in A client + this.blocked = true; + this.toggleRemoteStream(false); + } else if(message.type === MESSAGE_TYPE_UNBLOCKED) { + this.blocked = false; + this.toggleRemoteStream(true); } }); @@ -105,6 +106,31 @@ export class VideoPeer extends Peer { }); this.pushVideoToRemoteUser(); + this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => { + if (userId === this.userId) { + this.toggleRemoteStream(false); + this.sendBlockMessage(true); + } + }); + this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => { + if (userId === this.userId) { + this.toggleRemoteStream(true); + this.sendBlockMessage(false); + } + }); + + if (blackListManager.isBlackListed(this.userId)) { + this.sendBlockMessage(true) + } + } + + private sendBlockMessage(blocking: boolean) { + this.write(new Buffer(JSON.stringify({type: blocking ? MESSAGE_TYPE_BLOCKED : MESSAGE_TYPE_UNBLOCKED, name: this.userName.toUpperCase(), userId: this.userId, message: ''}))); + } + + private toggleRemoteStream(enable: boolean) { + this.remoteStream.getTracks().forEach(track => track.enabled = enable); + mediaManager.toggleBlockLogo(this.userId, !enable); } private sendWebrtcSignal(data: unknown) { @@ -120,13 +146,13 @@ export class VideoPeer extends Peer { */ private stream(stream: MediaStream) { try { + this.remoteStream = stream; + if (blackListManager.isBlackListed(this.userId) || this.blocked) { + this.toggleRemoteStream(false); + } mediaManager.addStreamRemoteVideo("" + this.userId, stream); }catch (err){ console.error(err); - //Force add streem video - /*setTimeout(() => { - this.stream(stream); - }, 500);*/ //todo: find a way to prevent infinite regression. } } @@ -139,6 +165,8 @@ export class VideoPeer extends Peer { if(!this.toClose){ return; } + this.onBlockSubscribe.unsubscribe(); + this.onUnBlockSubscribe.unsubscribe(); mediaManager.removeActiveVideo("" + this.userId); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. diff --git a/front/src/index.ts b/front/src/index.ts index acf66cf8..94b1df43 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -1,7 +1,6 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable"; -import {cypressAsserter} from "./Cypress/CypressAsserter"; import {LoginScene} from "./Phaser/Login/LoginScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; @@ -53,8 +52,28 @@ const fps : Phaser.Types.Core.FPSConfig = { smoothStep: false } +// the ?phaserMode=canvas parameter can be used to force Canvas usage +const params = new URLSearchParams(document.location.search.substring(1)); +const phaserMode = params.get("phaserMode"); +let mode: number; +switch (phaserMode) { + case 'auto': + case null: + mode = Phaser.AUTO; + break; + case 'canvas': + mode = Phaser.CANVAS; + break; + case 'webgl': + mode = Phaser.WEBGL; + break; + default: + throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"'); +} + + const config: GameConfig = { - type: Phaser.AUTO, + type: mode, title: "WorkAdventure", width: width / RESOLUTION, height: height / RESOLUTION, @@ -65,6 +84,11 @@ const config: GameConfig = { dom: { createContainer: true }, + render: { + pixelArt: true, + roundPixels: true, + antialias: false + }, physics: { default: "arcade", arcade: { @@ -73,16 +97,15 @@ const config: GameConfig = { }, callbacks: { postBoot: game => { - const renderer = game.renderer; + // Commented out to try to fix MacOS bug + /*const renderer = game.renderer; if (renderer instanceof WebGLRenderer) { renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game)); - } + }*/ } } }; -cypressAsserter.gameStarted(); - const game = new Phaser.Game(config); window.addEventListener('resize', function (event) { diff --git a/front/templater.sh b/front/templater.sh index e63617c5..1851cdc5 100755 --- a/front/templater.sh +++ b/front/templater.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -x set -o nounset errexit -template_file_index=dist/index.html.tmpl +template_file_index=dist/index.tmpl.html generated_file_index=dist/index.html tmp_trackcodefile=/tmp/trackcode diff --git a/front/tests/Phaser/Game/HtmlUtilsTest.ts b/front/tests/Phaser/Game/HtmlUtilsTest.ts index 8ef1d476..a878fdc0 100644 --- a/front/tests/Phaser/Game/HtmlUtilsTest.ts +++ b/front/tests/Phaser/Game/HtmlUtilsTest.ts @@ -2,13 +2,19 @@ import "jasmine"; import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils"; describe("urlify()", () => { - it("should transform an url into a link", () => { - const text = HtmlUtils.urlify('https://google.com'); - expect(text).toEqual('https://google.com'); + // FIXME: we need to add PhantomJS to have a good mock for "document". + /*it("should transform an url into a link", () => { + const text = HtmlUtils.urlify('foo https://google.com bar'); + expect(text).toEqual('foo https://google.com bar'); }); it("should not transform a normal text into a link", () => { const text = HtmlUtils.urlify('hello'); expect(text).toEqual('hello'); }); -}); \ No newline at end of file + + it("should escape HTML", () => { + const text = HtmlUtils.urlify('

boo

'); + expect(text).toEqual('<h1>boo</h1>'); + });*/ +}); diff --git a/front/webpack.config.js b/front/webpack.config.js index 3b97081c..170466ef 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -39,13 +39,34 @@ module.exports = { plugins: [ new HtmlWebpackPlugin( { - template: './dist/index.html' + template: './dist/index.tmpl.html', + minify: { + collapseWhitespace: true, + keepClosingSlash: true, + removeComments: false, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + useShortDoctype: true + } } ), new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin(['API_URL', 'UPLOADER_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE', 'START_ROOM_URL']) + 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' + ]) ], }; diff --git a/front/yarn.lock b/front/yarn.lock index 105a4310..0b85ad88 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1771,7 +1771,7 @@ eventemitter3@^2.0.3: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= -eventemitter3@^4.0.0, eventemitter3@^4.0.7: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -1829,7 +1829,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -exports-loader@^1.1.1: +exports-loader@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69" integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg== @@ -2528,7 +2528,7 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" -imports-loader@^1.2.0: +imports-loader@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f" integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ== @@ -3663,14 +3663,14 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -phaser@^3.52.0: - version "3.52.0" - resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.52.0.tgz#834dd7a7717308c2576d2450d0806317cf3d1ea8" - integrity sha512-oH39AUXR+cletB3GIvLnVO2J7/M6xs0D0LamVdCrbCDO3jckQOVBcqR6SJ2wam+4D5iJTCXv8K+FmxFzcTUWuA== +phaser@3.24.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.24.1.tgz#376e0c965d2a35af37c06ee78627dafbde5be017" + integrity sha512-WbrRMkbpEzarkfrq83akeauc6b8xNxsOTpDygyW7wrU2G2ne6kOYu3hji4UAaGnZaOLrVuj8ycYPjX9P1LxcDw== dependencies: - eventemitter3 "^4.0.7" - exports-loader "^1.1.1" - imports-loader "^1.2.0" + eventemitter3 "^4.0.4" + exports-loader "^1.1.0" + imports-loader "^1.1.0" path "^0.12.7" picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: @@ -4094,7 +4094,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.6.0: +rxjs@^6.6.0, rxjs@^6.6.3: version "6.6.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== diff --git a/maps/.dockerignore b/maps/.dockerignore new file mode 100644 index 00000000..64d1025d --- /dev/null +++ b/maps/.dockerignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/bundle.js +/yarn-error.log +/Dockerfile diff --git a/maps/Floor0/floor0.json b/maps/Floor0/floor0.json index f5239941..24b28d6c 100644 --- a/maps/Floor0/floor0.json +++ b/maps/Floor0/floor0.json @@ -298,7 +298,7 @@ { "name":"exitUrl", "type":"string", - "value":"\/@\/tcm\/workadventure\/floor1" + "value":"..\/Floor1\/floor1.json" }], "type":"tilelayer", "visible":true, diff --git a/maps/Floor1/floor1.json b/maps/Floor1/floor1.json index 6deb3bf5..1894ed42 100644 --- a/maps/Floor1/floor1.json +++ b/maps/Floor1/floor1.json @@ -83,9 +83,9 @@ "opacity":1, "properties":[ { - "name":"exitSceneUrl", + "name":"exitUrl", "type":"string", - "value":"\/@\/tcm\/workadventure\/floor0#down-the-stairs" + "value":"..\/Floor0\/floor0.json" }], "type":"tilelayer", "visible":true, @@ -264,7 +264,7 @@ "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", - "tiledversion":"1.4.2", + "tiledversion":"1.3.3", "tileheight":32, "tilesets":[ { @@ -1959,6 +1959,6 @@ }], "tilewidth":32, "type":"map", - "version":1.4, + "version":1.2, "width":46 } \ No newline at end of file diff --git a/maps/tests/Attribution-tilesets.txt b/maps/tests/Attribution-tilesets.txt new file mode 100644 index 00000000..a0e4224a --- /dev/null +++ b/maps/tests/Attribution-tilesets.txt @@ -0,0 +1,25 @@ +License +------- + +CC-BY-SA 3.0: + - http://creativecommons.org/licenses/by-sa/3.0/ + - See the file: cc-by-sa-3.0.txt +GNU GPL 3.0: + - http://www.gnu.org/licenses/gpl-3.0.html + - See the file: gpl-3.0.txt + +Assets from: workadventure@thecodingmachine.com + +BASE assets: +------------ + + - le-coq.png + - logotcm.png + - pin.png + - tileset1-repositioning.png + - tileset1.png + - tileset2.2.png + - tileset2.png + - tileset3.2.png + - tileset3.png + - walls2.png \ No newline at end of file diff --git a/maps/tests/jitsi_config.json b/maps/tests/jitsi_config.json new file mode 100644 index 00000000..1a831d97 --- /dev/null +++ b/maps/tests/jitsi_config.json @@ -0,0 +1,99 @@ +{ "compressionlevel":-1, + "editorsettings": + { + "export": + { + "target":"." + } + }, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":4, + "name":"jitsi", + "opacity":1, + "properties":[ + { + "name":"jitsiConfig", + "type":"string", + "value":"{ \"startWithAudioMuted\": true }" + }, + { + "name":"jitsiInterfaceConfig", + "type":"string", + "value":"{ \"DEFAULT_BACKGROUND\": \"#77ee77\" }" + }, + { + "name":"jitsiRoom", + "type":"string", + "value":"myRoom avec espace \u00e9\u00e0$&'\"_ \ud83d\ude00" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.3.3", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.2, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/tileset1.png b/maps/tests/tileset1.png new file mode 100644 index 00000000..6e7dafb4 Binary files /dev/null and b/maps/tests/tileset1.png differ diff --git a/messages/.dockerignore b/messages/.dockerignore new file mode 100644 index 00000000..64d1025d --- /dev/null +++ b/messages/.dockerignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/bundle.js +/yarn-error.log +/Dockerfile diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 39f575be..54b425f9 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -168,6 +168,8 @@ message WebRtcStartMessage { int32 userId = 1; string name = 2; bool initiator = 3; + string webrtcUserName = 4; + string webrtcPassword = 5; } message WebRtcDisconnectMessage { @@ -177,6 +179,8 @@ message WebRtcDisconnectMessage { message WebRtcSignalToClientMessage { int32 userId = 1; string signal = 2; + string webrtcUserName = 4; + string webrtcPassword = 5; } message TeleportMessageMessage{ diff --git a/pusher/Dockerfile b/pusher/Dockerfile index 42de3883..4aec9748 100644 --- a/pusher/Dockerfile +++ b/pusher/Dockerfile @@ -1,15 +1,26 @@ -FROM thecodingmachine/workadventure-back-base:latest as builder -WORKDIR /var/www/messages -COPY --chown=docker:docker messages . +# protobuf build +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder +WORKDIR /usr/src +COPY messages . RUN yarn install && yarn proto -FROM thecodingmachine/nodejs:12 - -COPY --chown=docker:docker pusher . -COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated +# typescript build +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2 +WORKDIR /usr/src +COPY pusher/yarn.lock pusher/package.json ./ RUN yarn install - +COPY pusher . +COPY --from=builder /usr/src/generated src/Messages/generated ENV NODE_ENV=production RUN yarn run tsc +# final production image +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 +WORKDIR /usr/src +COPY pusher/yarn.lock pusher/package.json ./ +COPY --from=builder2 /usr/src/dist /usr/src/dist +ENV NODE_ENV=production +RUN yarn install --production + +USER node CMD ["yarn", "run", "runprod"] diff --git a/pusher/Dockerfile.prod b/pusher/Dockerfile.prod new file mode 100644 index 00000000..772532f0 --- /dev/null +++ b/pusher/Dockerfile.prod @@ -0,0 +1,17 @@ +FROM node:12.19.0-slim + +RUN mkdir -p /home/node/app && chown -R node:node /home/node/app +WORKDIR /home/node/app + +USER node +ENV NODE_ENV=production +ENV DEBUG=* + +COPY --chown=node:node package.json yarn.lock ./ + +RUN yarn install --prod --frozen-lockfile + +COPY --chown=node:node ./dist/ ./dist/ + +EXPOSE 8080 +CMD ["yarn", "run", "runprod"] diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 48e8a1a4..e9bccef8 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -105,11 +105,12 @@ class AdminApi { return res.data; } - reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) { + reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) { return Axios.post(`${ADMIN_API_URL}/api/report`, { reportedUserUuid, reportedUserComment, reporterUserUuid, + reportWorldSlug }, { headers: {"Authorization": `${ADMIN_API_TOKEN}`} diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 2f86ae19..713b8c7d 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -172,6 +172,7 @@ export class SocketManager implements ZoneEventListener { console.log('Calling joinRoom') const apiClient = await apiClientRepository.getClient(client.roomId); const streamToPusher = apiClient.joinRoom(); + clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId); client.backConnection = streamToPusher; @@ -304,7 +305,7 @@ export class SocketManager implements ZoneEventListener { throw 'reported socket user not found'; } //TODO report user on admin application - await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid) + await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2]) } catch (e) { console.error('An error occurred on "handleReportMessage"'); console.error(e); diff --git a/uploader/Dockerfile b/uploader/Dockerfile index 3c471f6c..e4366308 100644 --- a/uploader/Dockerfile +++ b/uploader/Dockerfile @@ -1,9 +1,19 @@ -FROM thecodingmachine/nodejs:12 - -COPY --chown=docker:docker uploader . +# typescript build +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2 +WORKDIR /usr/src +COPY uploader/yarn.lock uploader/package.json ./ RUN yarn install - +COPY uploader . ENV NODE_ENV=production RUN yarn run tsc +# final production image +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 +WORKDIR /usr/src +COPY uploader/yarn.lock uploader/package.json ./ +COPY --from=builder2 /usr/src/dist /usr/src/dist +ENV NODE_ENV=production +RUN yarn install --production + +USER node CMD ["yarn", "run", "runprod"] diff --git a/website/.dockerignore b/website/.dockerignore new file mode 100644 index 00000000..576c21a2 --- /dev/null +++ b/website/.dockerignore @@ -0,0 +1,5 @@ +/dist/ +/node_modules/ +/dist/bundle.js +/yarn-error.log +/Dockerfile