Merge branch 'develop' into exitTriggerAction
# Conflicts: # front/src/Phaser/Game/GameScene.ts
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
**/node_modules/**
|
||||||
|
**/Dockerfile
|
@ -6,3 +6,7 @@ JITSI_ISS=
|
|||||||
SECRET_JITSI_KEY=
|
SECRET_JITSI_KEY=
|
||||||
ADMIN_API_TOKEN=123
|
ADMIN_API_TOKEN=123
|
||||||
START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
|
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=
|
||||||
|
32
.github/workflows/build-and-deploy.yml
vendored
@ -150,6 +150,7 @@ jobs:
|
|||||||
JITSI_ISS: ${{ secrets.JITSI_ISS }}
|
JITSI_ISS: ${{ secrets.JITSI_ISS }}
|
||||||
JITSI_URL: ${{ secrets.JITSI_URL }}
|
JITSI_URL: ${{ secrets.JITSI_URL }}
|
||||||
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
|
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
|
||||||
|
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
|
||||||
with:
|
with:
|
||||||
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
|
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
|
||||||
|
|
||||||
@ -159,34 +160,5 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
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
|
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"
|
|
||||||
|
10
README.md
@ -6,8 +6,6 @@ Demo here : [https://workadventu.re/](https://workadventu.re/).
|
|||||||
|
|
||||||
# Work Adventure
|
# 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
|
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.
|
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).
|
triggered when you move next to a colleague).
|
||||||
|
|
||||||
|
|
||||||
## Getting started
|
## Setting up a development environment
|
||||||
|
|
||||||
Install Docker.
|
Install Docker.
|
||||||
|
|
||||||
@ -101,5 +99,7 @@ Vagrant destroy
|
|||||||
* `Vagrant halt`: stop your VM Vagrant.
|
* `Vagrant halt`: stop your VM Vagrant.
|
||||||
* `Vagrant destroy`: delete your VM Vagrant.
|
* `Vagrant destroy`: delete your VM Vagrant.
|
||||||
|
|
||||||
## Features developed
|
## Setting up a production environment
|
||||||
You have more details of features developed in back [README.md](./back/README.md).
|
|
||||||
|
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.
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
FROM thecodingmachine/workadventure-back-base:latest as builder
|
# protobuf build
|
||||||
WORKDIR /var/www/messages
|
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
||||||
COPY --chown=docker:docker messages .
|
WORKDIR /usr/src
|
||||||
|
COPY messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
FROM thecodingmachine/nodejs:12
|
# typescript build
|
||||||
|
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
|
||||||
COPY --chown=docker:docker back .
|
WORKDIR /usr/src
|
||||||
COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
|
COPY back/yarn.lock back/package.json ./
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
COPY back .
|
||||||
|
COPY --from=builder /usr/src/generated src/Messages/generated
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN yarn run tsc
|
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"]
|
||||||
|
@ -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)
|
|
@ -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 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 GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
||||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
|
const 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 HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
|
||||||
const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
|
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 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 {
|
export {
|
||||||
SECRET_KEY,
|
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
ADMIN_API_URL,
|
ADMIN_API_URL,
|
||||||
ADMIN_API_TOKEN,
|
ADMIN_API_TOKEN,
|
||||||
|
@ -100,11 +100,12 @@ class AdminApi {
|
|||||||
return res.data;
|
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`, {
|
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
||||||
reportedUserUuid,
|
reportedUserUuid,
|
||||||
reportedUserComment,
|
reportedUserComment,
|
||||||
reporterUserUuid,
|
reporterUserUuid,
|
||||||
|
reportWorldSlug,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
||||||
|
@ -28,7 +28,13 @@ import {User, UserSocket} from "../Model/User";
|
|||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
import {Group} from "../Model/Group";
|
import {Group} from "../Model/Group";
|
||||||
import {cpuTracker} from "./CpuTracker";
|
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 {Movable} from "../Model/Movable";
|
||||||
import {PositionInterface} from "../Model/PositionInterface";
|
import {PositionInterface} from "../Model/PositionInterface";
|
||||||
import {adminApi, CharacterTexture} from "./AdminApi";
|
import {adminApi, CharacterTexture} from "./AdminApi";
|
||||||
@ -40,6 +46,8 @@ import {ZoneSocket} from "../RoomManager";
|
|||||||
import {Zone} from "_Model/Zone";
|
import {Zone} from "_Model/Zone";
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import {Admin} from "_Model/Admin";
|
import {Admin} from "_Model/Admin";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
|
||||||
const debug = Debug('sockermanager');
|
const debug = Debug('sockermanager');
|
||||||
|
|
||||||
@ -275,6 +283,12 @@ export class SocketManager {
|
|||||||
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
||||||
webrtcSignalToClient.setUserid(user.id);
|
webrtcSignalToClient.setUserid(user.id);
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
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();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
||||||
@ -295,6 +309,12 @@ export class SocketManager {
|
|||||||
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
||||||
webrtcSignalToClient.setUserid(user.id);
|
webrtcSignalToClient.setUserid(user.id);
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
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();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
||||||
@ -487,6 +507,11 @@ export class SocketManager {
|
|||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
webrtcStartMessage1.setName(otherUser.name);
|
webrtcStartMessage1.setName(otherUser.name);
|
||||||
webrtcStartMessage1.setInitiator(true);
|
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();
|
const serverToClientMessage1 = new ServerToClientMessage();
|
||||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
||||||
@ -500,6 +525,11 @@ export class SocketManager {
|
|||||||
webrtcStartMessage2.setUserid(user.id);
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
webrtcStartMessage2.setName(user.name);
|
webrtcStartMessage2.setName(user.name);
|
||||||
webrtcStartMessage2.setInitiator(false);
|
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();
|
const serverToClientMessage2 = new ServerToClientMessage();
|
||||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
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
|
//disconnect user
|
||||||
private disConnectedUser(user: User, group: Group) {
|
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
|
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
|
||||||
|
20
contrib/docker/.env.prod.template
Normal file
@ -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=
|
100
contrib/docker/docker-compose.prod.yaml
Normal file
@ -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
|
@ -3,7 +3,8 @@
|
|||||||
local namespace = env.GITHUB_REF_SLUG,
|
local namespace = env.GITHUB_REF_SLUG,
|
||||||
local tag = namespace,
|
local tag = namespace,
|
||||||
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com",
|
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",
|
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"containers": {
|
"containers": {
|
||||||
@ -21,6 +22,7 @@
|
|||||||
"JITSI_ISS": env.JITSI_ISS,
|
"JITSI_ISS": env.JITSI_ISS,
|
||||||
"JITSI_URL": env.JITSI_URL,
|
"JITSI_URL": env.JITSI_URL,
|
||||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||||
|
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||||
} + if adminUrl != null then {
|
} + if adminUrl != null then {
|
||||||
"ADMIN_API_URL": adminUrl,
|
"ADMIN_API_URL": adminUrl,
|
||||||
} else {}
|
} else {}
|
||||||
@ -39,6 +41,7 @@
|
|||||||
"JITSI_ISS": env.JITSI_ISS,
|
"JITSI_ISS": env.JITSI_ISS,
|
||||||
"JITSI_URL": env.JITSI_URL,
|
"JITSI_URL": env.JITSI_URL,
|
||||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||||
|
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||||
} + if adminUrl != null then {
|
} + if adminUrl != null then {
|
||||||
"ADMIN_API_URL": adminUrl,
|
"ADMIN_API_URL": adminUrl,
|
||||||
} else {}
|
} else {}
|
||||||
@ -79,6 +82,7 @@
|
|||||||
"TURN_USER": "workadventure",
|
"TURN_USER": "workadventure",
|
||||||
"TURN_PASSWORD": "WorkAdventure123",
|
"TURN_PASSWORD": "WorkAdventure123",
|
||||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
"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"
|
//"GA_TRACKING_ID": "UA-10196481-11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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
|
|
@ -31,9 +31,12 @@ services:
|
|||||||
ADMIN_URL: workadventure.localhost
|
ADMIN_URL: workadventure.localhost
|
||||||
STARTUP_COMMAND_1: ./templater.sh
|
STARTUP_COMMAND_1: ./templater.sh
|
||||||
STARTUP_COMMAND_2: yarn install
|
STARTUP_COMMAND_2: yarn install
|
||||||
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
|
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||||
TURN_USER: workadventure
|
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
||||||
TURN_PASSWORD: WorkAdventure123
|
# 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"
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
command: yarn run start
|
command: yarn run start
|
||||||
volumes:
|
volumes:
|
||||||
@ -108,6 +111,7 @@ services:
|
|||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
JITSI_URL: $JITSI_URL
|
JITSI_URL: $JITSI_URL
|
||||||
JITSI_ISS: $JITSI_ISS
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
TURN_STATIC_AUTH_SECRET: SomeStaticAuthSecret
|
||||||
volumes:
|
volumes:
|
||||||
- ./back:/usr/src/app
|
- ./back:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
@ -149,3 +153,28 @@ services:
|
|||||||
- ./back:/usr/src/back
|
- ./back:/usr/src/back
|
||||||
- ./front:/usr/src/front
|
- ./front:/usr/src/front
|
||||||
- ./pusher:/usr/src/pusher
|
- ./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
|
||||||
|
3
e2e/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
screenshots/
|
|
||||||
videos/
|
|
||||||
node_modules/
|
|
@ -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.
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"baseUrl": "http://workadventure.localhost",
|
|
||||||
"video": false,
|
|
||||||
"defaultCommandTimeout": 20000,
|
|
||||||
"pluginsFile": false,
|
|
||||||
"supportFile": false
|
|
||||||
}
|
|
@ -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')
|
|
||||||
});
|
|
||||||
});
|
|
1406
e2e/package-lock.json
generated
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"cypress": "^3.8.3"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"cy:run": "cypress run",
|
|
||||||
"cy:open": "cypress open"
|
|
||||||
}
|
|
||||||
}
|
|
1
front/dist/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
index.html
|
12
front/dist/resources/html/gameMenu.html
vendored
@ -15,6 +15,14 @@
|
|||||||
#gameMenu section {
|
#gameMenu section {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
section#socialLinks{
|
||||||
|
position: absolute;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
section#socialLinks img{
|
||||||
|
width: 32px;
|
||||||
|
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="gameMenu" hidden>
|
<div id="gameMenu" hidden>
|
||||||
@ -38,5 +46,9 @@
|
|||||||
<section id="adminConsoleSection" hidden>
|
<section id="adminConsoleSection" hidden>
|
||||||
<button id="adminConsoleButton">Admin console</button>
|
<button id="adminConsoleButton">Admin console</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="socialLinks" hidden>
|
||||||
|
<a class="not-button" href="https://www.facebook.com/workadventurebytcm" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||||
|
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
122
front/dist/resources/html/gameReport.html
vendored
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<style>
|
||||||
|
*{
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
cursor: url('/resources/logos/cursor_normal.png'), auto;
|
||||||
|
}
|
||||||
|
* a, button, input{
|
||||||
|
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||||
|
}
|
||||||
|
#gameReport {
|
||||||
|
background: #eceeee;
|
||||||
|
border: 1px solid #42464b;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 2px auto 0;
|
||||||
|
width: 298px;
|
||||||
|
}
|
||||||
|
#gameReport h1 {
|
||||||
|
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||||
|
border-bottom: 1px solid #a6abaf;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #727678;
|
||||||
|
display: block;
|
||||||
|
height: 43px;
|
||||||
|
padding-top: 10px;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||||
|
}
|
||||||
|
#gameReport h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#gameReport textarea {
|
||||||
|
font-size: 70%;
|
||||||
|
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||||
|
border: 1px solid #a1a3a3;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #696969;
|
||||||
|
height: 100px;
|
||||||
|
transition: box-shadow 0.3s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#gameReport section {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#gameReport section.action{
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#gameReport button {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 60%;
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 3px 10px 3px 10px;
|
||||||
|
}
|
||||||
|
#gameReport button#gameReportFormCancel {
|
||||||
|
background-color: #c7c7c700;
|
||||||
|
color: #292929;
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
#gameReport section a{
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0 6px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
#gameReport section h6,
|
||||||
|
#gameReport section h5{
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
#gameReport section.text-center{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#gameReport p{
|
||||||
|
font-size: 8px;
|
||||||
|
margin: 3px 0 0 0;
|
||||||
|
}
|
||||||
|
#gameReport form p{
|
||||||
|
margin: 0px 70px;
|
||||||
|
}
|
||||||
|
#gameReport section p.err{
|
||||||
|
color: red;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#gameReport section p.info{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<main id="gameReport" hidden>
|
||||||
|
<section>
|
||||||
|
<button id="gameReportFormCancel">X</button>
|
||||||
|
<h1>Moderate <span id="nameReported"></span></h1>
|
||||||
|
<p id="askActionP">What action do you want to take?</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Block: </h3>
|
||||||
|
<p>Block any communication from and to this user. This can be reverted.</p>
|
||||||
|
<section class="action">
|
||||||
|
<button id="toggleBlockButton">Block this user</button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section id="reportSection">
|
||||||
|
<h3>Report: </h3>
|
||||||
|
<p>Send a report message to the administrators of this room. They may later ban this user.</p>
|
||||||
|
<form>
|
||||||
|
<section>
|
||||||
|
<h6>Your message: </h6>
|
||||||
|
<textarea type="text" name="report" id="gameReportInput"></textarea>
|
||||||
|
<p class="err" id="gameReportErr"></p>
|
||||||
|
</section>
|
||||||
|
<section class="action">
|
||||||
|
<button type="submit" id="gameReportFormSubmit">Report this user</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
3
front/dist/resources/html/gameShare.html
vendored
@ -14,9 +14,6 @@
|
|||||||
width: 298px;
|
width: 298px;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
#gameShare .cautiousText {
|
|
||||||
font-size: 50%;
|
|
||||||
}
|
|
||||||
#gameShare h1 {
|
#gameShare h1 {
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||||
border-bottom: 1px solid #a6abaf;
|
border-bottom: 1px solid #a6abaf;
|
||||||
|
22
front/dist/resources/logos/blockSign.svg
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg2985" version="1.1" inkscape:version="0.48.4 r9939" width="485.33627" height="485.33627" sodipodi:docname="600px-France_road_sign_B1j.svg[1].png">
|
||||||
|
<metadata id="metadata2991">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||||
|
<dc:title/>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs id="defs2989"/>
|
||||||
|
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1272" inkscape:window-height="745" id="namedview2987" showgrid="false" inkscape:snap-global="true" inkscape:snap-grids="true" inkscape:snap-bbox="true" inkscape:bbox-paths="true" inkscape:bbox-nodes="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:object-paths="true" inkscape:snap-intersection-paths="true" inkscape:object-nodes="true" inkscape:snap-smooth-nodes="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-center="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:zoom="0.59970176" inkscape:cx="390.56499" inkscape:cy="244.34365" inkscape:window-x="86" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid type="xygrid" id="grid2995" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="-57.33186px" originy="-57.33186px"/>
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<g inkscape:groupmode="layer" id="layer1" inkscape:label="1" style="display:inline" transform="translate(-57.33186,-57.33186)">
|
||||||
|
<path sodipodi:type="arc" style="color:#000000;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2997" sodipodi:cx="300" sodipodi:cy="300" sodipodi:rx="240" sodipodi:ry="240" d="M 540,300 C 540,432.54834 432.54834,540 300,540 167.45166,540 60,432.54834 60,300 60,167.45166 167.45166,60 300,60 432.54834,60 540,167.45166 540,300 z" transform="matrix(1.0058783,0,0,1.0058783,-1.76349,-1.76349)"/>
|
||||||
|
<path sodipodi:type="arc" style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4005" sodipodi:cx="304.75" sodipodi:cy="214.75" sodipodi:rx="44.75" sodipodi:ry="44.75" d="m 349.5,214.75 c 0,24.71474 -20.03526,44.75 -44.75,44.75 -24.71474,0 -44.75,-20.03526 -44.75,-44.75 0,-24.71474 20.03526,-44.75 44.75,-44.75 24.71474,0 44.75,20.03526 44.75,44.75 z" transform="matrix(5.1364411,0,0,5.1364411,-1265.3304,-803.05073)"/>
|
||||||
|
<rect style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="rect4001" width="345" height="80.599998" x="127.5" y="259.70001"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
BIN
front/dist/resources/logos/blockingIcon.png
vendored
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
front/dist/resources/logos/cancel.png
vendored
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
front/dist/resources/logos/logo.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
1
front/dist/resources/logos/report.back.svg
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.48 56.48"><defs><style>.cls-1{fill:#e76e54;}.cls-2{fill:#fff;}</style></defs><path class="cls-1" d="M39.94,512H16.54L0,495.46v-23.4l16.54-16.54h23.4l16.54,16.54v23.4Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M33.54,485.52H23l-1.77-21.18H35.3Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M23,492.58H33.54v10.59H23Z" transform="translate(0 -455.52)"/></svg>
|
After Width: | Height: | Size: 477 B |
2
front/dist/resources/logos/report.svg
vendored
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 6.1 KiB |
BIN
front/dist/resources/objects/facebook-icon.png
vendored
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
front/dist/resources/objects/talk.png
vendored
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 516 B |
BIN
front/dist/resources/objects/twitter-icon.png
vendored
Normal file
After Width: | Height: | Size: 3.0 KiB |
73
front/dist/resources/style/style.css
vendored
@ -39,6 +39,7 @@ body .message-info.warning{
|
|||||||
position: relative;
|
position: relative;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
background-color: #00000099;
|
background-color: #00000099;
|
||||||
|
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
.video-container i{
|
.video-container i{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -53,25 +54,71 @@ body .message-info.warning{
|
|||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.video-container img.active{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.video-container img{
|
.video-container img{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
width: 15px;
|
width: 25px;
|
||||||
height: 15px;
|
height: 25px;
|
||||||
background: #d93025;
|
|
||||||
border-radius: 48px;
|
|
||||||
left: 5px;
|
left: 5px;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
z-index: 2;
|
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;
|
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{
|
.video-container video{
|
||||||
@ -150,10 +197,7 @@ video#myCamVideo{
|
|||||||
transition: all .2s;
|
transition: all .2s;
|
||||||
right: 224px;
|
right: 224px;
|
||||||
}
|
}
|
||||||
/*.btn-call{
|
|
||||||
transition: all .1s;
|
|
||||||
left: 0px;
|
|
||||||
}*/
|
|
||||||
.btn-cam-action div img{
|
.btn-cam-action div img{
|
||||||
height: 22px;
|
height: 22px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
@ -352,6 +396,7 @@ body {
|
|||||||
#cowebsite {
|
#cowebsite {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
#cowebsite.loading {
|
#cowebsite.loading {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
@ -1078,7 +1123,7 @@ div.modal-report-user{
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
.discussion .messages .message p.a{
|
.discussion .messages .message p a{
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +26,10 @@
|
|||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"generic-type-guard": "^3.2.0",
|
"generic-type-guard": "^3.2.0",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"phaser": "^3.52.0",
|
"phaser": "3.24.1",
|
||||||
"queue-typescript": "^1.0.1",
|
"queue-typescript": "^1.0.1",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
|
"rxjs": "^6.6.3",
|
||||||
"simple-peer": "^9.6.2",
|
"simple-peer": "^9.6.2",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"webpack-require-http": "^0.4.3"
|
"webpack-require-http": "^0.4.3"
|
||||||
|
@ -332,7 +332,7 @@ export class ConsoleGlobalMessageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
active(){
|
active(){
|
||||||
this.userInputManager.clearAllInputKeyboard();
|
this.userInputManager.clearAllKeys();
|
||||||
this.divMainConsole.style.top = '0';
|
this.divMainConsole.style.top = '0';
|
||||||
this.activeConsole = true;
|
this.activeConsole = true;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import * as TypeMessages from "./TypeMessage";
|
import * as TypeMessages from "./TypeMessage";
|
||||||
|
import List = Phaser.Structs.List;
|
||||||
|
import {UpdatedLocalStreamCallback} from "../WebRtc/MediaManager";
|
||||||
|
import {Banned} from "./TypeMessage";
|
||||||
|
|
||||||
export interface TypeMessageInterface {
|
export interface TypeMessageInterface {
|
||||||
showMessage(message: string): void;
|
showMessage(message: string): void;
|
||||||
@ -8,6 +11,7 @@ export interface TypeMessageInterface {
|
|||||||
export class UserMessageManager {
|
export class UserMessageManager {
|
||||||
|
|
||||||
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
||||||
|
receiveBannedMessageListener: Set<Function> = new Set<UpdatedLocalStreamCallback>();
|
||||||
|
|
||||||
constructor(private Connection: RoomConnection) {
|
constructor(private Connection: RoomConnection) {
|
||||||
const valueTypeMessageTab = Object.values(TypeMessages);
|
const valueTypeMessageTab = Object.values(TypeMessages);
|
||||||
@ -21,7 +25,14 @@ export class UserMessageManager {
|
|||||||
initialise() {
|
initialise() {
|
||||||
//receive signal to show message
|
//receive signal to show message
|
||||||
this.Connection.receiveUserMessage((type: string, message: string) => {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
classTypeMessage.showMessage(message);
|
classTypeMessage.showMessage(message);
|
||||||
|
return classTypeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReceiveBanListener(callback: Function){
|
||||||
|
this.receiveBannedMessageListener.add(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -96,7 +96,9 @@ export interface WebRtcSignalSentMessageInterface {
|
|||||||
|
|
||||||
export interface WebRtcSignalReceivedMessageInterface {
|
export interface WebRtcSignalReceivedMessageInterface {
|
||||||
userId: number,
|
userId: number,
|
||||||
signal: SignalData
|
signal: SignalData,
|
||||||
|
webRtcUser: string | undefined,
|
||||||
|
webRtcPassword: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StartMapInterface {
|
export interface StartMapInterface {
|
||||||
|
@ -427,7 +427,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
callback({
|
callback({
|
||||||
userId: message.getUserid(),
|
userId: message.getUserid(),
|
||||||
name: message.getName(),
|
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) => {
|
this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||||
callback({
|
callback({
|
||||||
userId: message.getUserid(),
|
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) => {
|
this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||||
callback({
|
callback({
|
||||||
userId: message.getUserid(),
|
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;
|
return this.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
|
@ -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 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 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 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 STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||||
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
|
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';
|
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_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||||
const RESOLUTION = 2;
|
const RESOLUTION = 2;
|
||||||
@ -23,6 +24,7 @@ export {
|
|||||||
ZOOM_LEVEL,
|
ZOOM_LEVEL,
|
||||||
POSITION_DELAY,
|
POSITION_DELAY,
|
||||||
MAX_EXTRAPOLATION_TIME,
|
MAX_EXTRAPOLATION_TIME,
|
||||||
|
STUN_SERVER,
|
||||||
TURN_SERVER,
|
TURN_SERVER,
|
||||||
TURN_USER,
|
TURN_USER,
|
||||||
TURN_PASSWORD,
|
TURN_PASSWORD,
|
||||||
|
@ -1,14 +1,54 @@
|
|||||||
|
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
||||||
|
|
||||||
export const addLoader = (scene:Phaser.Scene): void => {
|
const LogoNameIndex: string = 'logoLoading';
|
||||||
const loadingText = scene.add.text(scene.game.renderer.width / 2, 200, 'Loading');
|
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<Phaser.GameObjects.Image>((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();
|
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) => {
|
scene.load.on('progress', (value: number) => {
|
||||||
progress.clear();
|
progress.clear();
|
||||||
progress.fillStyle(0xffffff, 1);
|
progress.fillStyle(0xBBBBBB, 1);
|
||||||
progress.fillRect(0, 270, 800 * value, 60);
|
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
|
||||||
});
|
});
|
||||||
scene.load.on('complete', () => {
|
scene.load.on('complete', () => {
|
||||||
|
if(loadingText){
|
||||||
loadingText.destroy();
|
loadingText.destroy();
|
||||||
|
}
|
||||||
|
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||||
|
resLoadingImage.destroy();
|
||||||
|
});
|
||||||
progress.destroy();
|
progress.destroy();
|
||||||
|
progressContainer.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -3,14 +3,12 @@ import {discussionManager} from "../../WebRtc/DiscussionManager";
|
|||||||
export const openChatIconName = 'openChatIcon';
|
export const openChatIconName = 'openChatIcon';
|
||||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, openChatIconName);
|
super(scene, x, y, openChatIconName, 3);
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
this.setScrollFactor(0, 0);
|
this.setScrollFactor(0, 0);
|
||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.displayWidth = 30;
|
|
||||||
this.displayHeight = 30;
|
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false)
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(99999);
|
||||||
|
|
||||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
||||||
|
@ -6,7 +6,8 @@ export interface BodyResourceDescriptionListInterface {
|
|||||||
|
|
||||||
export interface BodyResourceDescriptionInterface {
|
export interface BodyResourceDescriptionInterface {
|
||||||
name: string,
|
name: string,
|
||||||
img: string
|
img: string,
|
||||||
|
level?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
|
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||||
|
@ -23,21 +23,26 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
|||||||
});
|
});
|
||||||
return returnArray;
|
return returnArray;
|
||||||
}
|
}
|
||||||
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
|
|
||||||
|
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
||||||
const name = 'customCharacterTexture'+texture.id;
|
const name = 'customCharacterTexture'+texture.id;
|
||||||
load.spritesheet(name,texture.url,{frameWidth: 32, frameHeight: 32});
|
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
||||||
return name;
|
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturePlugin: TextureManager, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||||
const promisesList:Promise<void>[] = [];
|
const promisesList:Promise<unknown>[] = [];
|
||||||
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
|
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
|
||||||
|
try {
|
||||||
|
//TODO refactor
|
||||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||||
if(!texturePlugin.exists(playerResourceDescriptor.name)) {
|
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
console.log('Loading '+playerResourceDescriptor.name)
|
|
||||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
||||||
}
|
}
|
||||||
})
|
}catch (err){
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
|
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
|
||||||
if (promisesList.length > 0) {
|
if (promisesList.length > 0) {
|
||||||
loadPlugin.start();
|
loadPlugin.start();
|
||||||
@ -66,8 +71,14 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
|
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
||||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {frameWidth: 32, frameHeight: 32});
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
loadPlugin.once('filecomplete-spritesheet-'+playerResourceDescriptor.name, () => res());
|
return res(playerResourceDescriptor);
|
||||||
|
}
|
||||||
|
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
|
||||||
|
frameWidth: 32,
|
||||||
|
frameHeight: 32
|
||||||
|
});
|
||||||
|
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import {GameScene} from "../Game/GameScene";
|
import {GameScene} from "../Game/GameScene";
|
||||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||||
import {Character} from "../Entity/Character";
|
import {Character} from "../Entity/Character";
|
||||||
import {Sprite} from "./Sprite";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
* 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
|
//set data
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|
||||||
|
//todo: implement on click action
|
||||||
|
/*this.playerName.setInteractive();
|
||||||
|
this.playerName.on('pointerup', () => {
|
||||||
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
updatePosition(position: PointInterface): void {
|
||||||
|
@ -14,7 +14,7 @@ export class SpeechBubble {
|
|||||||
const bubbleWidth = bubblePadding * 2 + text.length * 10;
|
const bubbleWidth = bubblePadding * 2 + text.length * 10;
|
||||||
const arrowHeight = bubbleHeight / 4;
|
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);
|
player.add(this.bubble);
|
||||||
|
|
||||||
// Bubble shadow
|
// Bubble shadow
|
||||||
|
@ -49,6 +49,10 @@ export class GameMap {
|
|||||||
this.lastProperties = newProps;
|
this.lastProperties = newProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCurrentProperties(): Map<string, string|boolean|number> {
|
||||||
|
return this.lastProperties;
|
||||||
|
}
|
||||||
|
|
||||||
private getProperties(key: number): Map<string, string|boolean|number> {
|
private getProperties(key: number): Map<string, string|boolean|number> {
|
||||||
const properties = new Map<string, string|boolean|number>();
|
const properties = new Map<string, string|boolean|number>();
|
||||||
|
|
||||||
|
@ -30,10 +30,11 @@ import {RemotePlayer} from "../Entity/RemotePlayer";
|
|||||||
import {Queue} from 'queue-typescript';
|
import {Queue} from 'queue-typescript';
|
||||||
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
||||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||||
import {lazyLoadPlayerCharacterTextures} from "../Entity/PlayerTexturesLoadingManager";
|
import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import {
|
import {
|
||||||
CenterListener,
|
CenterListener,
|
||||||
EXIT_MESSAGE_PROPERTIES, JITSI_MESSAGE_PROPERTIES,
|
EXIT_MESSAGE_PROPERTIES,
|
||||||
|
JITSI_MESSAGE_PROPERTIES,
|
||||||
layoutManager,
|
layoutManager,
|
||||||
LayoutMode,
|
LayoutMode,
|
||||||
ON_ACTION_TRIGGER_BUTTON,
|
ON_ACTION_TRIGGER_BUTTON,
|
||||||
@ -72,6 +73,8 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
|
|||||||
import {TextureError} from "../../Exception/TextureError";
|
import {TextureError} from "../../Exception/TextureError";
|
||||||
import {addLoader} from "../Components/Loader";
|
import {addLoader} from "../Components/Loader";
|
||||||
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
||||||
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
|
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface|null,
|
initPosition: PointInterface|null,
|
||||||
@ -116,7 +119,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
MapPlayers!: Phaser.Physics.Arcade.Group;
|
MapPlayers!: Phaser.Physics.Arcade.Group;
|
||||||
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
||||||
Map!: Phaser.Tilemaps.Tilemap;
|
Map!: Phaser.Tilemaps.Tilemap;
|
||||||
Layers!: Array<Phaser.Tilemaps.TilemapLayer>;
|
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
|
||||||
Objects!: Array<Phaser.Physics.Arcade.Sprite>;
|
Objects!: Array<Phaser.Physics.Arcade.Sprite>;
|
||||||
mapFile!: ITiledMap;
|
mapFile!: ITiledMap;
|
||||||
groups: Map<number, Sprite>;
|
groups: Map<number, Sprite>;
|
||||||
@ -157,7 +160,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
||||||
// The item that can be selected by pressing the space key.
|
// The item that can be selected by pressing the space key.
|
||||||
private outlinedItem: ActionableItem|null = null;
|
private outlinedItem: ActionableItem|null = null;
|
||||||
private userInputManager!: UserInputManager;
|
public userInputManager!: UserInputManager;
|
||||||
private isReconnecting: boolean = false;
|
private isReconnecting: boolean = false;
|
||||||
private startLayerName!: string | null;
|
private startLayerName!: string | null;
|
||||||
private openChatIcon!: OpenChatIcon;
|
private openChatIcon!: OpenChatIcon;
|
||||||
@ -186,7 +189,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
//hook preload scene
|
//hook preload scene
|
||||||
preload(): void {
|
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.image(openChatIconName, 'resources/objects/talk.png');
|
||||||
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
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.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');
|
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.
|
// 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);
|
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||||
|
|
||||||
//add layer on map
|
//add layer on map
|
||||||
this.Layers = new Array<Phaser.Tilemaps.TilemapLayer>();
|
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
|
||||||
let depth = -2;
|
let depth = -2;
|
||||||
for (const layer of this.mapFile.layers) {
|
for (const layer of this.mapFile.layers) {
|
||||||
if (layer.type === 'tilelayer') {
|
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);
|
const exitSceneUrl = this.getExitSceneUrl(layer);
|
||||||
if (exitSceneUrl !== undefined) {
|
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.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
||||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
||||||
this.UserMessageManager = new UserMessageManager(this.connection);
|
this.UserMessageManager = new UserMessageManager(this.connection);
|
||||||
|
this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.simplePeer.registerPeerConnectionListener({
|
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(){
|
private triggerOnMapLayerPropertyChange(){
|
||||||
/* @deprecated
|
/* @deprecated
|
||||||
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
|
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
|
||||||
@ -656,7 +677,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
}else{
|
}else{
|
||||||
const openWebsiteFunction = () => {
|
const openWebsiteFunction = () => {
|
||||||
coWebsiteManager.loadCoWebsite(newValue as string);
|
coWebsiteManager.loadCoWebsite(newValue as string, allProps.get('openWebsitePolicy') as string | undefined);
|
||||||
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -680,11 +701,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.stopJitsi();
|
this.stopJitsi();
|
||||||
}else{
|
}else{
|
||||||
const openJitsiRoomFunction = () => {
|
const openJitsiRoomFunction = () => {
|
||||||
|
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||||
if (JITSI_PRIVATE_MODE) {
|
if (JITSI_PRIVATE_MODE) {
|
||||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||||
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
|
|
||||||
|
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||||
} else {
|
} else {
|
||||||
this.startJitsi(newValue as string);
|
this.startJitsi(roomName, undefined);
|
||||||
}
|
}
|
||||||
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
|
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);
|
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
|
||||||
urlManager.pushStartLayerNameToUrl(hash);
|
urlManager.pushStartLayerNameToUrl(hash);
|
||||||
if (roomId !== this.scene.key) {
|
if (roomId !== this.scene.key) {
|
||||||
|
if (this.scene.get(roomId) === null) {
|
||||||
|
console.error("next room not loaded", exitKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.cleanupClosingScene();
|
this.cleanupClosingScene();
|
||||||
this.scene.stop();
|
this.scene.stop();
|
||||||
this.scene.remove(this.scene.key);
|
this.scene.remove(this.scene.key);
|
||||||
@ -878,13 +905,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.cameras.main.setZoom(ZOOM_LEVEL);
|
this.cameras.main.setZoom(ZOOM_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){
|
addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){
|
||||||
this.Layers.push(Layer);
|
this.Layers.push(Layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
createCollisionWithPlayer() {
|
createCollisionWithPlayer() {
|
||||||
//add collision layer
|
//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.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
|
||||||
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
|
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
|
||||||
});
|
});
|
||||||
@ -902,7 +929,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
createCurrentPlayer(){
|
createCurrentPlayer(){
|
||||||
//TODO create animation moving between exit and start
|
//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 {
|
try {
|
||||||
this.CurrentPlayer = new Player(
|
this.CurrentPlayer = new Player(
|
||||||
this,
|
this,
|
||||||
@ -1096,7 +1123,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, addPlayerData.characterLayers);
|
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
|
||||||
const player = new RemotePlayer(
|
const player = new RemotePlayer(
|
||||||
addPlayerData.userId,
|
addPlayerData.userId,
|
||||||
this,
|
this,
|
||||||
@ -1221,6 +1248,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
private reposition(): void {
|
private reposition(): void {
|
||||||
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||||
this.chatModeSprite.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
|
// Recompute camera offset if needed
|
||||||
this.updateCameraOffset();
|
this.updateCameraOffset();
|
||||||
@ -1247,7 +1275,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public startJitsi(roomName: string, jwt?: string): void {
|
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);
|
this.connection.setSilent(true);
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
|
|
||||||
@ -1265,5 +1297,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,8 +43,9 @@ export class ActionableItem {
|
|||||||
}
|
}
|
||||||
this.isSelectable = true;
|
this.isSelectable = true;
|
||||||
if (this.sprite.pipeline) {
|
if (this.sprite.pipeline) {
|
||||||
this.sprite.setPipeline(OutlinePipeline.KEY);
|
// Commented out to try to fix MacOS issue
|
||||||
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);
|
/*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;
|
return;
|
||||||
}
|
}
|
||||||
this.isSelectable = false;
|
this.isSelectable = false;
|
||||||
this.sprite.resetPipeline();
|
// Commented out to try to fix MacOS issue
|
||||||
|
//this.sprite.resetPipeline();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
41
front/src/Phaser/Login/AbstractCharacterScene.ts
Normal file
@ -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<BodyResourceDescriptionInterface[]> {
|
||||||
|
const textures = this.getTextures();
|
||||||
|
const promises : Promise<BodyResourceDescriptionInterface>[] = [];
|
||||||
|
if (textures) {
|
||||||
|
for (const texture of textures) {
|
||||||
|
if (texture.level === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
promises.push(loadCustomTexture(this.load, texture));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSelectSceneCharacters() : Promise<BodyResourceDescriptionInterface[]> {
|
||||||
|
const textures = this.getTextures();
|
||||||
|
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import {ResizableScene} from "./ResizableScene";
|
|||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
import {addLoader} from "../Components/Loader";
|
import {addLoader} from "../Components/Loader";
|
||||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||||
|
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||||
|
|
||||||
export const CustomizeSceneName = "CustomizeScene";
|
export const CustomizeSceneName = "CustomizeScene";
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ enum CustomizeTextures{
|
|||||||
arrowUp = "arrow_up",
|
arrowUp = "arrow_up",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CustomizeScene extends ResizableScene {
|
export class CustomizeScene extends AbstractCharacterScene {
|
||||||
|
|
||||||
private textField!: TextField;
|
private textField!: TextField;
|
||||||
private enterField!: TextField;
|
private enterField!: TextField;
|
||||||
@ -48,29 +49,21 @@ export class CustomizeScene extends ResizableScene {
|
|||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
addLoader(this);
|
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.arrowRight, "resources/objects/arrow_right.png");
|
||||||
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
|
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
|
||||||
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.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.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() {
|
create() {
|
||||||
|
@ -2,7 +2,6 @@ import {gameManager} from "../Game/GameManager";
|
|||||||
import {TextField} from "../Components/TextField";
|
import {TextField} from "../Components/TextField";
|
||||||
import {TextInput} from "../Components/TextInput";
|
import {TextInput} from "../Components/TextInput";
|
||||||
import Image = Phaser.GameObjects.Image;
|
import Image = Phaser.GameObjects.Image;
|
||||||
import {cypressAsserter} from "../../Cypress/CypressAsserter";
|
|
||||||
import {SelectCharacterSceneName} from "./SelectCharacterScene";
|
import {SelectCharacterSceneName} from "./SelectCharacterScene";
|
||||||
import {ResizableScene} from "./ResizableScene";
|
import {ResizableScene} from "./ResizableScene";
|
||||||
|
|
||||||
@ -29,16 +28,13 @@ export class LoginScene extends ResizableScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
cypressAsserter.preloadStarted();
|
|
||||||
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.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
|
// 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');
|
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||||
cypressAsserter.preloadFinished();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
cypressAsserter.initStarted();
|
|
||||||
|
|
||||||
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
|
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) => {
|
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);
|
this.login(this.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
cypressAsserter.initFinished();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(time: number, delta: number): void {
|
update(time: number, delta: number): void {
|
||||||
|
@ -9,6 +9,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore";
|
|||||||
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import {addLoader} from "../Components/Loader";
|
import {addLoader} from "../Components/Loader";
|
||||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||||
|
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||||
|
|
||||||
|
|
||||||
//todo: put this constants in a dedicated file
|
//todo: put this constants in a dedicated file
|
||||||
@ -21,7 +22,7 @@ enum LoginTextures {
|
|||||||
customizeButtonSelected = "customize_button_selected"
|
customizeButtonSelected = "customize_button_selected"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SelectCharacterScene extends ResizableScene {
|
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||||
private readonly nbCharactersPerRow = 6;
|
private readonly nbCharactersPerRow = 6;
|
||||||
private textField!: TextField;
|
private textField!: TextField;
|
||||||
private pressReturnField!: TextField;
|
private pressReturnField!: TextField;
|
||||||
@ -44,6 +45,13 @@ export class SelectCharacterScene extends ResizableScene {
|
|||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
addLoader(this);
|
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.playButton, "resources/objects/play_button.png");
|
||||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.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
|
// 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.customizeButton, 'resources/objects/customize.png');
|
||||||
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
|
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
|
||||||
|
|
||||||
const localUser = localUserStore.getLocalUser();
|
addLoader(this);
|
||||||
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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
@ -3,7 +3,9 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
|
|||||||
import {gameManager} from "../Game/GameManager";
|
import {gameManager} from "../Game/GameManager";
|
||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
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';
|
export const MenuSceneName = 'MenuScene';
|
||||||
const gameMenuKey = 'gameMenu';
|
const gameMenuKey = 'gameMenu';
|
||||||
@ -21,6 +23,7 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
private menuElement!: Phaser.GameObjects.DOMElement;
|
private menuElement!: Phaser.GameObjects.DOMElement;
|
||||||
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
|
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
|
||||||
private gameShareElement!: Phaser.GameObjects.DOMElement;
|
private gameShareElement!: Phaser.GameObjects.DOMElement;
|
||||||
|
private gameReportElement!: ReportMenu;
|
||||||
private sideMenuOpened = false;
|
private sideMenuOpened = false;
|
||||||
private settingsMenuOpened = false;
|
private settingsMenuOpened = false;
|
||||||
private gameShareOpened = 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(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
|
||||||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||||
this.load.html(gameShare, 'resources/html/gameShare.html');
|
this.load.html(gameShare, 'resources/html/gameShare.html');
|
||||||
|
this.load.html(gameReportKey, gameReportRessource);
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
||||||
this.menuElement.setOrigin(0);
|
this.menuElement.setOrigin(0);
|
||||||
this.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||||
|
|
||||||
const middleX = (window.innerWidth / 3) - 298;
|
const middleX = (window.innerWidth / 3) - 298;
|
||||||
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
|
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.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
|
||||||
this.revealMenusAfterInit(this.gameShareElement, gameShare);
|
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
|
||||||
this.gameShareElement.addListener('click');
|
this.gameShareElement.addListener('click');
|
||||||
this.gameShareElement.on('click', (event:MouseEvent) => {
|
this.gameShareElement.on('click', (event:MouseEvent) => {
|
||||||
event.preventDefault();
|
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.input.keyboard.on('keyup-TAB', () => {
|
||||||
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
|
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
|
||||||
});
|
});
|
||||||
@ -77,7 +87,8 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
this.menuElement.on('click', this.onMenuClick.bind(this));
|
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.
|
//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.
|
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -98,6 +109,11 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
|
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
|
||||||
adminSection.hidden = false;
|
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({
|
this.tweens.add({
|
||||||
targets: this.menuElement,
|
targets: this.menuElement,
|
||||||
x: openedSideMenuX,
|
x: openedSideMenuX,
|
||||||
@ -222,6 +238,9 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onMenuClick(event:MouseEvent) {
|
private onMenuClick(event:MouseEvent) {
|
||||||
|
if((event?.target as HTMLInputElement).classList.contains('not-button')){
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
switch ((event?.target as HTMLInputElement).id) {
|
switch ((event?.target as HTMLInputElement).id) {
|
||||||
@ -280,5 +299,6 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
private closeAll(){
|
private closeAll(){
|
||||||
this.closeGameQualityMenu();
|
this.closeGameQualityMenu();
|
||||||
this.closeGameShare();
|
this.closeGameShare();
|
||||||
|
this.gameReportElement.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
119
front/src/Phaser/Menu/ReportMenu.ts
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
// the unique id of this pipeline
|
||||||
public static readonly KEY = 'Outline';
|
public static readonly KEY = 'Outline';
|
||||||
|
@ -59,7 +59,11 @@ export class UserInputManager {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAllInputKeyboard(){
|
clearAllListeners(){
|
||||||
|
this.Scene.input.keyboard.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllKeys(){
|
||||||
this.Scene.input.keyboard.removeAllKeys();
|
this.Scene.input.keyboard.removeAllKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
front/src/WebRtc/BlackListManager.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {Subject} from 'rxjs';
|
||||||
|
|
||||||
|
class BlackListManager {
|
||||||
|
private list: number[] = [];
|
||||||
|
public onBlockStream: Subject<number> = new Subject();
|
||||||
|
public onUnBlockStream: Subject<number> = 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();
|
@ -42,7 +42,7 @@ class CoWebsiteManager {
|
|||||||
this.opened = iframeStates.opened;
|
this.opened = iframeStates.opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadCoWebsite(url: string): void {
|
public loadCoWebsite(url: string, allowPolicy?: string): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
<img src="resources/logos/close.svg">
|
<img src="resources/logos/close.svg">
|
||||||
@ -56,6 +56,9 @@ class CoWebsiteManager {
|
|||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.id = 'cowebsite-iframe';
|
iframe.id = 'cowebsite-iframe';
|
||||||
iframe.src = url;
|
iframe.src = url;
|
||||||
|
if (allowPolicy) {
|
||||||
|
iframe.allow = allowPolicy;
|
||||||
|
}
|
||||||
const onloadPromise = new Promise((resolve) => {
|
const onloadPromise = new Promise((resolve) => {
|
||||||
iframe.onload = () => resolve();
|
iframe.onload = () => resolve();
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
import {mediaManager, ReportCallback} from "./MediaManager";
|
import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
|
||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||||
@ -61,7 +61,7 @@ export class DiscussionManager {
|
|||||||
const inputMessage: HTMLInputElement = document.createElement('input');
|
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||||
inputMessage.onfocus = () => {
|
inputMessage.onfocus = () => {
|
||||||
if(this.userInputManager) {
|
if(this.userInputManager) {
|
||||||
this.userInputManager.clearAllInputKeyboard();
|
this.userInputManager.clearAllKeys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputMessage.onblur = () => {
|
inputMessage.onblur = () => {
|
||||||
@ -99,7 +99,7 @@ export class DiscussionManager {
|
|||||||
name: string|undefined,
|
name: string|undefined,
|
||||||
img?: string|undefined,
|
img?: string|undefined,
|
||||||
isMe: boolean = false,
|
isMe: boolean = false,
|
||||||
reportCallback?: ReportCallback
|
showReportCallBack?: ShowReportCallBack
|
||||||
) {
|
) {
|
||||||
const divParticipant: HTMLDivElement = document.createElement('div');
|
const divParticipant: HTMLDivElement = document.createElement('div');
|
||||||
divParticipant.classList.add('participant');
|
divParticipant.classList.add('participant');
|
||||||
@ -128,8 +128,8 @@ export class DiscussionManager {
|
|||||||
reportBanUserAction.classList.add('report-btn')
|
reportBanUserAction.classList.add('report-btn')
|
||||||
reportBanUserAction.innerText = 'Report';
|
reportBanUserAction.innerText = 'Report';
|
||||||
reportBanUserAction.addEventListener('click', () => {
|
reportBanUserAction.addEventListener('click', () => {
|
||||||
if(reportCallback) {
|
if(showReportCallBack) {
|
||||||
mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
|
showReportCallBack(`${userId}`, name);
|
||||||
}else{
|
}else{
|
||||||
console.info('report feature is not activated!');
|
console.info('report feature is not activated!');
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,45 @@
|
|||||||
export class HtmlUtils {
|
export class HtmlUtils {
|
||||||
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||||
const elem = document.getElementById(id);
|
const elem = document.getElementById(id);
|
||||||
if (elem === null) {
|
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||||
}
|
}
|
||||||
// FIXME: does not check the type of the returned type
|
|
||||||
return elem as T;
|
public static querySelectorOrFail<T extends HTMLElement>(selector: string): T {
|
||||||
|
const elem = document.querySelector<T>(selector);
|
||||||
|
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
throw new Error("Cannot find HTML element with selector '"+selector+"'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||||
const elem = document.getElementById(id);
|
const elem = document.getElementById(id);
|
||||||
if (elem === null) {
|
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||||
|
elem.remove();
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||||
}
|
}
|
||||||
// FIXME: does not check the type of the returned type
|
|
||||||
elem.remove();
|
private static escapeHtml(html: string): string {
|
||||||
return elem as T;
|
const text = document.createTextNode(html);
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.appendChild(text);
|
||||||
|
return p.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static urlify(text: string): string {
|
public static urlify(text: string): string {
|
||||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||||
|
text = HtmlUtils.escapeHtml(text);
|
||||||
return text.replace(urlRegex, (url: string) => {
|
return text.replace(urlRegex, (url: string) => {
|
||||||
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
|
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
|
||||||
|
return elem !== null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,21 @@ import {mediaManager} from "./MediaManager";
|
|||||||
import {coWebsiteManager} from "./CoWebsiteManager";
|
import {coWebsiteManager} from "./CoWebsiteManager";
|
||||||
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
const interfaceConfig = {
|
const getDefaultConfig = () => {
|
||||||
|
return {
|
||||||
|
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
||||||
|
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
||||||
|
prejoinPageEnabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultInterfaceConfig = {
|
||||||
SHOW_CHROME_EXTENSION_BANNER: false,
|
SHOW_CHROME_EXTENSION_BANNER: false,
|
||||||
MOBILE_APP_PROMO: false,
|
MOBILE_APP_PROMO: false,
|
||||||
|
|
||||||
HIDE_INVITE_MORE_HEADER: true,
|
HIDE_INVITE_MORE_HEADER: true,
|
||||||
|
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
|
||||||
|
DISABLE_VIDEO_BACKGROUND: true,
|
||||||
|
|
||||||
// Note: hiding brand does not seem to work, we probably need to put this on the server side.
|
// Note: hiding brand does not seem to work, we probably need to put this on the server side.
|
||||||
SHOW_BRAND_WATERMARK: false,
|
SHOW_BRAND_WATERMARK: false,
|
||||||
@ -25,13 +35,40 @@ const interfaceConfig = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const slugify = (...args: (string | number)[]): string => {
|
||||||
|
const value = args.join(' ')
|
||||||
|
|
||||||
|
return value
|
||||||
|
.normalize('NFD') // split an accented letter in the base letter and the accent
|
||||||
|
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced)
|
||||||
|
.replace(/\s+/g, '-') // separator
|
||||||
|
}
|
||||||
|
|
||||||
class JitsiFactory {
|
class JitsiFactory {
|
||||||
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
private audioCallback = this.onAudioChange.bind(this);
|
private audioCallback = this.onAudioChange.bind(this);
|
||||||
private videoCallback = this.onVideoChange.bind(this);
|
private videoCallback = this.onVideoChange.bind(this);
|
||||||
|
|
||||||
public start(roomName: string, playerName:string, jwt?: string): void {
|
/**
|
||||||
|
* Slugifies the room name and prepends the room name with the instance
|
||||||
|
*/
|
||||||
|
public getRoomName(roomName: string, instance: string): string {
|
||||||
|
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object): void {
|
||||||
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||||
|
// Jitsi meet external API maintains some data in local storage
|
||||||
|
// which is sent via the appData URL parameter when joining a
|
||||||
|
// conference. Problem is that this data grows indefinitely. Thus
|
||||||
|
// after some time the URLs get so huge that loading the iframe
|
||||||
|
// becomes slow and eventually breaks completely. Thus lets just
|
||||||
|
// clear jitsi local storage before starting a new conference.
|
||||||
|
window.localStorage.removeItem("jitsiLocalStorage");
|
||||||
|
|
||||||
const domain = JITSI_URL;
|
const domain = JITSI_URL;
|
||||||
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
roomName: roomName,
|
roomName: roomName,
|
||||||
@ -39,12 +76,8 @@ class JitsiFactory {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
parentNode: cowebsiteDiv,
|
parentNode: cowebsiteDiv,
|
||||||
configOverwrite: {
|
configOverwrite: {...config, ...getDefaultConfig()},
|
||||||
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
interfaceConfigOverwrite: {...defaultInterfaceConfig, ...interfaceConfig}
|
||||||
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
|
||||||
prejoinPageEnabled: false
|
|
||||||
},
|
|
||||||
interfaceConfigOverwrite: interfaceConfig,
|
|
||||||
};
|
};
|
||||||
if (!options.jwt) {
|
if (!options.jwt) {
|
||||||
delete options.jwt;
|
delete options.jwt;
|
||||||
@ -87,7 +120,6 @@ class JitsiFactory {
|
|||||||
mediaManager.enableCamera();
|
mediaManager.enableCamera();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jitsiFactory = new JitsiFactory();
|
export const jitsiFactory = new JitsiFactory();
|
@ -218,7 +218,7 @@ class LayoutManager {
|
|||||||
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
||||||
*/
|
*/
|
||||||
public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} {
|
public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} {
|
||||||
const game = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game');
|
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
|
||||||
if (this.mode === LayoutMode.VideoChat) {
|
if (this.mode === LayoutMode.VideoChat) {
|
||||||
const children = document.querySelectorAll<HTMLDivElement>('div.chat-mode > div');
|
const children = document.querySelectorAll<HTMLDivElement>('div.chat-mode > div');
|
||||||
const htmlChildren = Array.from(children.values());
|
const htmlChildren = Array.from(children.values());
|
||||||
|
@ -3,6 +3,7 @@ import {HtmlUtils} from "./HtmlUtils";
|
|||||||
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
|
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
|
||||||
|
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
|
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
|
||||||
@ -23,9 +24,9 @@ export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
|
|||||||
export type StartScreenSharingCallback = (media: MediaStream) => void;
|
export type StartScreenSharingCallback = (media: MediaStream) => void;
|
||||||
export type StopScreenSharingCallback = (media: MediaStream) => void;
|
export type StopScreenSharingCallback = (media: MediaStream) => void;
|
||||||
export type ReportCallback = (message: string) => void;
|
export type ReportCallback = (message: string) => void;
|
||||||
|
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
|
||||||
|
|
||||||
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
|
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
|
||||||
// TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!)
|
|
||||||
export class MediaManager {
|
export class MediaManager {
|
||||||
localStream: MediaStream|null = null;
|
localStream: MediaStream|null = null;
|
||||||
localScreenCapture: MediaStream|null = null;
|
localScreenCapture: MediaStream|null = null;
|
||||||
@ -46,6 +47,7 @@ export class MediaManager {
|
|||||||
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
|
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
|
||||||
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||||
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
|
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
|
||||||
|
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
|
||||||
private microphoneBtn: HTMLDivElement;
|
private microphoneBtn: HTMLDivElement;
|
||||||
private cinemaBtn: HTMLDivElement;
|
private cinemaBtn: HTMLDivElement;
|
||||||
private monitorBtn: HTMLDivElement;
|
private monitorBtn: HTMLDivElement;
|
||||||
@ -469,8 +471,9 @@ export class MediaManager {
|
|||||||
return this.getCamera();
|
return this.getCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
addActiveVideo(userId: string, reportCallBack: ReportCallback|undefined, userName: string = ""){
|
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
||||||
this.webrtcInAudio.play();
|
this.webrtcInAudio.play();
|
||||||
|
const userId = ''+user.userId
|
||||||
|
|
||||||
userName = userName.toUpperCase();
|
userName = userName.toUpperCase();
|
||||||
const color = this.getColorByString(userName);
|
const color = this.getColorByString(userName);
|
||||||
@ -480,33 +483,39 @@ export class MediaManager {
|
|||||||
<div class="connecting-spinner"></div>
|
<div class="connecting-spinner"></div>
|
||||||
<div class="rtc-error" style="display: none"></div>
|
<div class="rtc-error" style="display: none"></div>
|
||||||
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
|
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
|
||||||
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
|
<img id="microphone-${userId}" title="mute" src="resources/logos/microphone-close.svg">
|
||||||
` +
|
<button id="report-${userId}" class="report">
|
||||||
((reportCallBack!==undefined)?`<img id="report-${userId}" class="report active" src="resources/logos/report.svg">`:'')
|
<img title="report this user" src="resources/logos/report.svg">
|
||||||
+
|
<span>Report/Block</span>
|
||||||
`<video id="${userId}" autoplay></video>
|
</button>
|
||||||
|
<video id="${userId}" autoplay></video>
|
||||||
|
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
layoutManager.add(DivImportance.Normal, userId, html);
|
layoutManager.add(DivImportance.Normal, userId, html);
|
||||||
|
|
||||||
if (reportCallBack) {
|
|
||||||
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
|
||||||
reportBtn.addEventListener('click', (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.showReportModal(userId, userName, reportCallBack);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||||
|
|
||||||
//permit to create participant in discussion part
|
//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<HTMLImageElement>(`report-${userId}`);
|
||||||
|
reportBanUserActionEl.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
showReportUser();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
||||||
|
|
||||||
userId = `screen-sharing-${userId}`;
|
userId = this.getScreenSharingId(userId);
|
||||||
const html = `
|
const html = `
|
||||||
<div id="div-${userId}" class="video-container">
|
<div id="div-${userId}" class="video-container">
|
||||||
<video id="${userId}" autoplay></video>
|
<video id="${userId}" autoplay></video>
|
||||||
@ -518,6 +527,10 @@ export class MediaManager {
|
|||||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getScreenSharingId(userId: string): string {
|
||||||
|
return `screen-sharing-${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
disabledMicrophoneByUserId(userId: number){
|
disabledMicrophoneByUserId(userId: number){
|
||||||
const element = document.getElementById(`microphone-${userId}`);
|
const element = document.getElementById(`microphone-${userId}`);
|
||||||
if(!element){
|
if(!element){
|
||||||
@ -556,6 +569,10 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleBlockLogo(userId: number, show: boolean): void {
|
||||||
|
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId);
|
||||||
|
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
|
||||||
|
}
|
||||||
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
|
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
|
||||||
const remoteVideo = this.remoteVideo.get(userId);
|
const remoteVideo = this.remoteVideo.get(userId);
|
||||||
if (remoteVideo === undefined) {
|
if (remoteVideo === undefined) {
|
||||||
@ -565,12 +582,12 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
|
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
|
// 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) {
|
if (remoteVideo === undefined) {
|
||||||
this.addScreenSharingActiveVideo(userId);
|
this.addScreenSharingActiveVideo(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream);
|
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeActiveVideo(userId: string){
|
removeActiveVideo(userId: string){
|
||||||
@ -581,7 +598,7 @@ export class MediaManager {
|
|||||||
this.removeParticipant(userId);
|
this.removeParticipant(userId);
|
||||||
}
|
}
|
||||||
removeActiveScreenSharingVideo(userId: string) {
|
removeActiveScreenSharingVideo(userId: string) {
|
||||||
this.removeActiveVideo(`screen-sharing-${userId}`)
|
this.removeActiveVideo(this.getScreenSharingId(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
playWebrtcOutSound(): void {
|
playWebrtcOutSound(): void {
|
||||||
@ -617,7 +634,7 @@ export class MediaManager {
|
|||||||
errorDiv.style.display = 'block';
|
errorDiv.style.display = 'block';
|
||||||
}
|
}
|
||||||
isErrorScreenSharing(userId: string): void {
|
isErrorScreenSharing(userId: string): void {
|
||||||
this.isError(`screen-sharing-${userId}`);
|
this.isError(this.getScreenSharingId(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -645,65 +662,8 @@ export class MediaManager {
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){
|
||||||
//create report text area
|
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
|
||||||
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('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 removeParticipant(userId: number|string){
|
public removeParticipant(userId: number|string){
|
||||||
@ -769,6 +729,10 @@ export class MediaManager {
|
|||||||
this.checkActiveUser();
|
this.checkActiveUser();
|
||||||
}, this.focused ? 10000 : 1000);
|
}, this.focused ? 10000 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setShowReportModalCallBacks(callback: ShowReportCallBack){
|
||||||
|
this.showReportModalCallBacks.add(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mediaManager = new MediaManager();
|
export const mediaManager = new MediaManager();
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import * as SimplePeerNamespace from "simple-peer";
|
import * as SimplePeerNamespace from "simple-peer";
|
||||||
import {mediaManager} from "./MediaManager";
|
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 {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
||||||
|
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
@ -16,25 +17,28 @@ export class ScreenSharingPeer extends Peer {
|
|||||||
private isReceivingStream:boolean = false;
|
private isReceivingStream:boolean = false;
|
||||||
public toClose: boolean = false;
|
public toClose: boolean = false;
|
||||||
public _connected: 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({
|
super({
|
||||||
initiator: initiator ? initiator : false,
|
initiator: initiator ? initiator : false,
|
||||||
reconnectTimer: 10000,
|
reconnectTimer: 10000,
|
||||||
config: {
|
config: {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
urls: 'stun:stun.l.google.com:19302'
|
urls: STUN_SERVER.split(',')
|
||||||
},
|
},
|
||||||
{
|
TURN_SERVER !== '' ? {
|
||||||
urls: TURN_SERVER.split(','),
|
urls: TURN_SERVER.split(','),
|
||||||
username: TURN_USER,
|
username: user.webRtcUser || TURN_USER,
|
||||||
credential: TURN_PASSWORD
|
credential: user.webRtcPassword || TURN_PASSWORD
|
||||||
},
|
} : undefined,
|
||||||
]
|
].filter((value) => value !== undefined)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.userId = user.userId;
|
||||||
|
|
||||||
//start listen signal for the peer connection
|
//start listen signal for the peer connection
|
||||||
this.on('signal', (data: unknown) => {
|
this.on('signal', (data: unknown) => {
|
||||||
this.sendWebrtcScreenSharingSignal(data);
|
this.sendWebrtcScreenSharingSignal(data);
|
||||||
|
@ -9,13 +9,18 @@ import {
|
|||||||
UpdatedLocalStreamCallback
|
UpdatedLocalStreamCallback
|
||||||
} from "./MediaManager";
|
} from "./MediaManager";
|
||||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
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 {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||||
|
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||||
|
import {blackListManager} from "./BlackListManager";
|
||||||
|
|
||||||
export interface UserSimplePeerInterface{
|
export interface UserSimplePeerInterface{
|
||||||
userId: number;
|
userId: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
initiator?: boolean;
|
initiator?: boolean;
|
||||||
|
webRtcUser?: string|undefined;
|
||||||
|
webRtcPassword?: string|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PeerConnectionListener {
|
export interface PeerConnectionListener {
|
||||||
@ -36,6 +41,7 @@ export class SimplePeer {
|
|||||||
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
|
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
|
||||||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||||
|
private readonly userId: number;
|
||||||
|
|
||||||
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
|
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.
|
// 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.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
|
||||||
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
|
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
|
||||||
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
|
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
|
||||||
|
this.userId = Connection.getUserId();
|
||||||
this.initialise();
|
this.initialise();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,15 +96,14 @@ export class SimplePeer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private receiveWebrtcStart(user: UserSimplePeerInterface) {
|
private receiveWebrtcStart(user: UserSimplePeerInterface): void {
|
||||||
//this.WebRtcRoomId = data.roomId;
|
|
||||||
this.Users.push(user);
|
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)
|
// 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)
|
// 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.
|
// This would be symmetrical to the way we handle disconnection.
|
||||||
|
|
||||||
//start connection
|
//start connection
|
||||||
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
//console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||||
if(!user.initiator){
|
if(!user.initiator){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -134,17 +140,13 @@ export class SimplePeer {
|
|||||||
|
|
||||||
mediaManager.removeActiveVideo("" + user.userId);
|
mediaManager.removeActiveVideo("" + user.userId);
|
||||||
|
|
||||||
const reportCallback = this.enableReporting ? (comment: string) => {
|
mediaManager.addActiveVideo(user, name);
|
||||||
this.reportUser(user.userId, comment);
|
|
||||||
} : undefined;
|
|
||||||
|
|
||||||
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
|
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||||
|
|
||||||
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
|
||||||
|
|
||||||
//permit to send message
|
//permit to send message
|
||||||
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
|
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;
|
peer.toClose = false;
|
||||||
@ -189,7 +191,7 @@ export class SimplePeer {
|
|||||||
mediaManager.addScreenSharingActiveVideo("" + user.userId);
|
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);
|
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
|
||||||
|
|
||||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||||
@ -300,6 +302,7 @@ export class SimplePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
||||||
|
if (blackListManager.isBlackListed(data.userId)) return;
|
||||||
console.log("receiveWebrtcScreenSharingSignal", data);
|
console.log("receiveWebrtcScreenSharingSignal", data);
|
||||||
try {
|
try {
|
||||||
//if offer type, create peer connection
|
//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 {
|
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 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)) {
|
if (this.PeerScreenSharingConnectionArray.has(userId)) {
|
||||||
this.pushScreenSharingToRemoteUser(userId);
|
this.pushScreenSharingToRemoteUser(userId);
|
||||||
|
@ -1,65 +1,57 @@
|
|||||||
import * as SimplePeerNamespace from "simple-peer";
|
import * as SimplePeerNamespace from "simple-peer";
|
||||||
import {mediaManager} from "./MediaManager";
|
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 {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
import {blackListManager} from "./BlackListManager";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
|
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
|
||||||
export const MESSAGE_TYPE_MESSAGE = 'message';
|
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.
|
* A peer connection used to transmit video / audio signals between 2 peers.
|
||||||
*/
|
*/
|
||||||
export class VideoPeer extends Peer {
|
export class VideoPeer extends Peer {
|
||||||
public toClose: boolean = false;
|
public toClose: boolean = false;
|
||||||
public _connected: 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({
|
super({
|
||||||
initiator: initiator ? initiator : false,
|
initiator: initiator ? initiator : false,
|
||||||
reconnectTimer: 10000,
|
reconnectTimer: 10000,
|
||||||
config: {
|
config: {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
urls: 'stun:stun.l.google.com:19302'
|
urls: STUN_SERVER.split(',')
|
||||||
},
|
},
|
||||||
{
|
TURN_SERVER !== '' ? {
|
||||||
urls: TURN_SERVER.split(','),
|
urls: TURN_SERVER.split(','),
|
||||||
username: TURN_USER,
|
username: user.webRtcUser || TURN_USER,
|
||||||
credential: TURN_PASSWORD
|
credential: user.webRtcPassword || TURN_PASSWORD
|
||||||
},
|
} : undefined,
|
||||||
]
|
].filter((value) => value !== undefined)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('PEER SETUP ', {
|
this.userId = user.userId;
|
||||||
initiator: initiator ? initiator : false,
|
this.userName = user.name || '';
|
||||||
reconnectTimer: 10000,
|
|
||||||
config: {
|
|
||||||
iceServers: [
|
|
||||||
{
|
|
||||||
urls: 'stun:stun.l.google.com:19302'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urls: TURN_SERVER.split(','),
|
|
||||||
username: TURN_USER,
|
|
||||||
credential: TURN_PASSWORD
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//start listen signal for the peer connection
|
//start listen signal for the peer connection
|
||||||
this.on('signal', (data: unknown) => {
|
this.on('signal', (data: unknown) => {
|
||||||
this.sendWebrtcSignal(data);
|
this.sendWebrtcSignal(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('stream', (stream: MediaStream) => {
|
this.on('stream', (stream: MediaStream) => this.stream(stream));
|
||||||
this.stream(stream);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => {
|
|
||||||
});*/
|
|
||||||
|
|
||||||
this.on('close', () => {
|
this.on('close', () => {
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
@ -70,7 +62,7 @@ export class VideoPeer extends Peer {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
this.on('error', (err: any) => {
|
this.on('error', (err: any) => {
|
||||||
console.error(`error => ${this.userId} => ${err.code}`, err);
|
console.error(`error => ${this.userId} => ${err.code}`, err);
|
||||||
mediaManager.isError("" + userId);
|
mediaManager.isError("" + this.userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('connect', () => {
|
this.on('connect', () => {
|
||||||
@ -81,8 +73,6 @@ export class VideoPeer extends Peer {
|
|||||||
|
|
||||||
this.on('data', (chunk: Buffer) => {
|
this.on('data', (chunk: Buffer) => {
|
||||||
const message = JSON.parse(chunk.toString('utf8'));
|
const message = JSON.parse(chunk.toString('utf8'));
|
||||||
console.log("data", message);
|
|
||||||
|
|
||||||
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||||
if (message.audio) {
|
if (message.audio) {
|
||||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||||
@ -95,9 +85,20 @@ export class VideoPeer extends Peer {
|
|||||||
} else {
|
} else {
|
||||||
mediaManager.disabledVideoByUserId(this.userId);
|
mediaManager.disabledVideoByUserId(this.userId);
|
||||||
}
|
}
|
||||||
} else if(message.type === 'message') {
|
} else if(message.type === MESSAGE_TYPE_MESSAGE) {
|
||||||
|
if (!blackListManager.isBlackListed(message.userId)) {
|
||||||
mediaManager.addNewMessage(message.name, message.message);
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.once('finish', () => {
|
this.once('finish', () => {
|
||||||
@ -105,6 +106,31 @@ export class VideoPeer extends Peer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.pushVideoToRemoteUser();
|
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) {
|
private sendWebrtcSignal(data: unknown) {
|
||||||
@ -120,13 +146,13 @@ export class VideoPeer extends Peer {
|
|||||||
*/
|
*/
|
||||||
private stream(stream: MediaStream) {
|
private stream(stream: MediaStream) {
|
||||||
try {
|
try {
|
||||||
|
this.remoteStream = stream;
|
||||||
|
if (blackListManager.isBlackListed(this.userId) || this.blocked) {
|
||||||
|
this.toggleRemoteStream(false);
|
||||||
|
}
|
||||||
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
|
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
|
||||||
}catch (err){
|
}catch (err){
|
||||||
console.error(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){
|
if(!this.toClose){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.onBlockSubscribe.unsubscribe();
|
||||||
|
this.onUnBlockSubscribe.unsubscribe();
|
||||||
mediaManager.removeActiveVideo("" + this.userId);
|
mediaManager.removeActiveVideo("" + this.userId);
|
||||||
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
|
// 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.
|
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'phaser';
|
import 'phaser';
|
||||||
import GameConfig = Phaser.Types.Core.GameConfig;
|
import GameConfig = Phaser.Types.Core.GameConfig;
|
||||||
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
|
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
|
||||||
import {cypressAsserter} from "./Cypress/CypressAsserter";
|
|
||||||
import {LoginScene} from "./Phaser/Login/LoginScene";
|
import {LoginScene} from "./Phaser/Login/LoginScene";
|
||||||
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
||||||
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
||||||
@ -53,8 +52,28 @@ const fps : Phaser.Types.Core.FPSConfig = {
|
|||||||
smoothStep: false
|
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 = {
|
const config: GameConfig = {
|
||||||
type: Phaser.AUTO,
|
type: mode,
|
||||||
title: "WorkAdventure",
|
title: "WorkAdventure",
|
||||||
width: width / RESOLUTION,
|
width: width / RESOLUTION,
|
||||||
height: height / RESOLUTION,
|
height: height / RESOLUTION,
|
||||||
@ -65,6 +84,11 @@ const config: GameConfig = {
|
|||||||
dom: {
|
dom: {
|
||||||
createContainer: true
|
createContainer: true
|
||||||
},
|
},
|
||||||
|
render: {
|
||||||
|
pixelArt: true,
|
||||||
|
roundPixels: true,
|
||||||
|
antialias: false
|
||||||
|
},
|
||||||
physics: {
|
physics: {
|
||||||
default: "arcade",
|
default: "arcade",
|
||||||
arcade: {
|
arcade: {
|
||||||
@ -73,16 +97,15 @@ const config: GameConfig = {
|
|||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
postBoot: game => {
|
postBoot: game => {
|
||||||
const renderer = game.renderer;
|
// Commented out to try to fix MacOS bug
|
||||||
|
/*const renderer = game.renderer;
|
||||||
if (renderer instanceof WebGLRenderer) {
|
if (renderer instanceof WebGLRenderer) {
|
||||||
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
|
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cypressAsserter.gameStarted();
|
|
||||||
|
|
||||||
const game = new Phaser.Game(config);
|
const game = new Phaser.Game(config);
|
||||||
|
|
||||||
window.addEventListener('resize', function (event) {
|
window.addEventListener('resize', function (event) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
set -x
|
||||||
set -o nounset errexit
|
set -o nounset errexit
|
||||||
template_file_index=dist/index.html.tmpl
|
template_file_index=dist/index.tmpl.html
|
||||||
generated_file_index=dist/index.html
|
generated_file_index=dist/index.html
|
||||||
tmp_trackcodefile=/tmp/trackcode
|
tmp_trackcodefile=/tmp/trackcode
|
||||||
|
|
||||||
|
@ -2,13 +2,19 @@ import "jasmine";
|
|||||||
import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils";
|
import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils";
|
||||||
|
|
||||||
describe("urlify()", () => {
|
describe("urlify()", () => {
|
||||||
it("should transform an url into a link", () => {
|
// FIXME: we need to add PhantomJS to have a good mock for "document".
|
||||||
const text = HtmlUtils.urlify('https://google.com');
|
/*it("should transform an url into a link", () => {
|
||||||
expect(text).toEqual('<a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a>');
|
const text = HtmlUtils.urlify('foo https://google.com bar');
|
||||||
|
expect(text).toEqual('foo <a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a> bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not transform a normal text into a link", () => {
|
it("should not transform a normal text into a link", () => {
|
||||||
const text = HtmlUtils.urlify('hello');
|
const text = HtmlUtils.urlify('hello');
|
||||||
expect(text).toEqual('hello');
|
expect(text).toEqual('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should escape HTML", () => {
|
||||||
|
const text = HtmlUtils.urlify('<h1>boo</h1>');
|
||||||
|
expect(text).toEqual('<h1>boo</h1>');
|
||||||
|
});*/
|
||||||
});
|
});
|
@ -39,13 +39,34 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin(
|
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({
|
new webpack.ProvidePlugin({
|
||||||
Phaser: 'phaser'
|
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'
|
||||||
|
])
|
||||||
],
|
],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -1771,7 +1771,7 @@ eventemitter3@^2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
|
||||||
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
|
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
|
||||||
|
|
||||||
eventemitter3@^4.0.0, eventemitter3@^4.0.7:
|
eventemitter3@^4.0.0, eventemitter3@^4.0.4:
|
||||||
version "4.0.7"
|
version "4.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||||
@ -1829,7 +1829,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
homedir-polyfill "^1.0.1"
|
homedir-polyfill "^1.0.1"
|
||||||
|
|
||||||
exports-loader@^1.1.1:
|
exports-loader@^1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69"
|
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69"
|
||||||
integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg==
|
integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg==
|
||||||
@ -2528,7 +2528,7 @@ import-local@^2.0.0:
|
|||||||
pkg-dir "^3.0.0"
|
pkg-dir "^3.0.0"
|
||||||
resolve-cwd "^2.0.0"
|
resolve-cwd "^2.0.0"
|
||||||
|
|
||||||
imports-loader@^1.2.0:
|
imports-loader@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
|
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
|
||||||
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
|
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
|
||||||
@ -3663,14 +3663,14 @@ pbkdf2@^3.0.3:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
phaser@^3.52.0:
|
phaser@3.24.1:
|
||||||
version "3.52.0"
|
version "3.24.1"
|
||||||
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.52.0.tgz#834dd7a7717308c2576d2450d0806317cf3d1ea8"
|
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.24.1.tgz#376e0c965d2a35af37c06ee78627dafbde5be017"
|
||||||
integrity sha512-oH39AUXR+cletB3GIvLnVO2J7/M6xs0D0LamVdCrbCDO3jckQOVBcqR6SJ2wam+4D5iJTCXv8K+FmxFzcTUWuA==
|
integrity sha512-WbrRMkbpEzarkfrq83akeauc6b8xNxsOTpDygyW7wrU2G2ne6kOYu3hji4UAaGnZaOLrVuj8ycYPjX9P1LxcDw==
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3 "^4.0.7"
|
eventemitter3 "^4.0.4"
|
||||||
exports-loader "^1.1.1"
|
exports-loader "^1.1.0"
|
||||||
imports-loader "^1.2.0"
|
imports-loader "^1.1.0"
|
||||||
path "^0.12.7"
|
path "^0.12.7"
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
|
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:
|
dependencies:
|
||||||
aproba "^1.1.1"
|
aproba "^1.1.1"
|
||||||
|
|
||||||
rxjs@^6.6.0:
|
rxjs@^6.6.0, rxjs@^6.6.3:
|
||||||
version "6.6.3"
|
version "6.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||||
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
||||||
|
4
maps/.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
/dist/bundle.js
|
||||||
|
/yarn-error.log
|
||||||
|
/Dockerfile
|
@ -298,7 +298,7 @@
|
|||||||
{
|
{
|
||||||
"name":"exitUrl",
|
"name":"exitUrl",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"\/@\/tcm\/workadventure\/floor1"
|
"value":"..\/Floor1\/floor1.json"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
|
@ -83,9 +83,9 @@
|
|||||||
"opacity":1,
|
"opacity":1,
|
||||||
"properties":[
|
"properties":[
|
||||||
{
|
{
|
||||||
"name":"exitSceneUrl",
|
"name":"exitUrl",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"\/@\/tcm\/workadventure\/floor0#down-the-stairs"
|
"value":"..\/Floor0\/floor0.json"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
@ -264,7 +264,7 @@
|
|||||||
"nextobjectid":1,
|
"nextobjectid":1,
|
||||||
"orientation":"orthogonal",
|
"orientation":"orthogonal",
|
||||||
"renderorder":"right-down",
|
"renderorder":"right-down",
|
||||||
"tiledversion":"1.4.2",
|
"tiledversion":"1.3.3",
|
||||||
"tileheight":32,
|
"tileheight":32,
|
||||||
"tilesets":[
|
"tilesets":[
|
||||||
{
|
{
|
||||||
@ -1959,6 +1959,6 @@
|
|||||||
}],
|
}],
|
||||||
"tilewidth":32,
|
"tilewidth":32,
|
||||||
"type":"map",
|
"type":"map",
|
||||||
"version":1.4,
|
"version":1.2,
|
||||||
"width":46
|
"width":46
|
||||||
}
|
}
|
25
maps/tests/Attribution-tilesets.txt
Normal file
@ -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
|
99
maps/tests/jitsi_config.json
Normal file
@ -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
|
||||||
|
}
|
BIN
maps/tests/tileset1.png
Normal file
After Width: | Height: | Size: 29 KiB |
4
messages/.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
/dist/bundle.js
|
||||||
|
/yarn-error.log
|
||||||
|
/Dockerfile
|
@ -168,6 +168,8 @@ message WebRtcStartMessage {
|
|||||||
int32 userId = 1;
|
int32 userId = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
bool initiator = 3;
|
bool initiator = 3;
|
||||||
|
string webrtcUserName = 4;
|
||||||
|
string webrtcPassword = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WebRtcDisconnectMessage {
|
message WebRtcDisconnectMessage {
|
||||||
@ -177,6 +179,8 @@ message WebRtcDisconnectMessage {
|
|||||||
message WebRtcSignalToClientMessage {
|
message WebRtcSignalToClientMessage {
|
||||||
int32 userId = 1;
|
int32 userId = 1;
|
||||||
string signal = 2;
|
string signal = 2;
|
||||||
|
string webrtcUserName = 4;
|
||||||
|
string webrtcPassword = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TeleportMessageMessage{
|
message TeleportMessageMessage{
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
FROM thecodingmachine/workadventure-back-base:latest as builder
|
# protobuf build
|
||||||
WORKDIR /var/www/messages
|
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
||||||
COPY --chown=docker:docker messages .
|
WORKDIR /usr/src
|
||||||
|
COPY messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
FROM thecodingmachine/nodejs:12
|
# typescript build
|
||||||
|
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
|
||||||
COPY --chown=docker:docker pusher .
|
WORKDIR /usr/src
|
||||||
COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
|
COPY pusher/yarn.lock pusher/package.json ./
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
COPY pusher .
|
||||||
|
COPY --from=builder /usr/src/generated src/Messages/generated
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN yarn run tsc
|
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"]
|
CMD ["yarn", "run", "runprod"]
|
||||||
|
17
pusher/Dockerfile.prod
Normal file
@ -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"]
|
@ -105,11 +105,12 @@ class AdminApi {
|
|||||||
return res.data;
|
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`, {
|
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
||||||
reportedUserUuid,
|
reportedUserUuid,
|
||||||
reportedUserComment,
|
reportedUserComment,
|
||||||
reporterUserUuid,
|
reporterUserUuid,
|
||||||
|
reportWorldSlug
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
||||||
|
@ -172,6 +172,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
console.log('Calling joinRoom')
|
console.log('Calling joinRoom')
|
||||||
const apiClient = await apiClientRepository.getClient(client.roomId);
|
const apiClient = await apiClientRepository.getClient(client.roomId);
|
||||||
const streamToPusher = apiClient.joinRoom();
|
const streamToPusher = apiClient.joinRoom();
|
||||||
|
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
|
||||||
|
|
||||||
client.backConnection = streamToPusher;
|
client.backConnection = streamToPusher;
|
||||||
|
|
||||||
@ -304,7 +305,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
throw 'reported socket user not found';
|
throw 'reported socket user not found';
|
||||||
}
|
}
|
||||||
//TODO report user on admin application
|
//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) {
|
} catch (e) {
|
||||||
console.error('An error occurred on "handleReportMessage"');
|
console.error('An error occurred on "handleReportMessage"');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
FROM thecodingmachine/nodejs:12
|
# typescript build
|
||||||
|
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
|
||||||
COPY --chown=docker:docker uploader .
|
WORKDIR /usr/src
|
||||||
|
COPY uploader/yarn.lock uploader/package.json ./
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
COPY uploader .
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN yarn run tsc
|
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"]
|
CMD ["yarn", "run", "runprod"]
|
||||||
|
5
website/.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/dist/
|
||||||
|
/node_modules/
|
||||||
|
/dist/bundle.js
|
||||||
|
/yarn-error.log
|
||||||
|
/Dockerfile
|