Merge branch 'develop' into fix/dependencies-cleanup
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
**/node_modules/**
|
||||||
|
**/Dockerfile
|
@ -5,3 +5,11 @@ JITSI_PRIVATE_MODE=false
|
|||||||
JITSI_ISS=
|
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
|
||||||
|
# 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=
|
||||||
|
|
||||||
|
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
||||||
|
ACME_EMAIL=
|
||||||
|
56
.github/workflows/build-and-deploy.yml
vendored
@ -102,29 +102,6 @@ jobs:
|
|||||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
tags: ${{ env.GITHUB_REF_SLUG }}
|
||||||
add_git_labels: true
|
add_git_labels: true
|
||||||
|
|
||||||
build-website:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
# Create a slugified value of the branch
|
|
||||||
- uses: rlespinasse/github-slug-action@3.1.0
|
|
||||||
|
|
||||||
- name: "Build and push back image"
|
|
||||||
uses: docker/build-push-action@v1
|
|
||||||
with:
|
|
||||||
dockerfile: website/Dockerfile
|
|
||||||
path: website/
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
repository: thecodingmachine/workadventure-website
|
|
||||||
tags: ${{ env.GITHUB_REF_SLUG }}
|
|
||||||
add_git_labels: true
|
|
||||||
|
|
||||||
build-maps:
|
build-maps:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -156,7 +133,6 @@ jobs:
|
|||||||
- build-pusher
|
- build-pusher
|
||||||
- build-maps
|
- build-maps
|
||||||
- build-uploader
|
- build-uploader
|
||||||
- build-website
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -174,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 }}
|
||||||
|
|
||||||
@ -183,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"
|
|
||||||
|
47
.github/workflows/continuous_integration.yml
vendored
@ -39,6 +39,10 @@ jobs:
|
|||||||
run: yarn run proto && yarn run copy-to-front
|
run: yarn run proto && yarn run copy-to-front
|
||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
|
- name: "Create index.html"
|
||||||
|
run: ./templater.sh
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: yarn run build
|
run: yarn run build
|
||||||
env:
|
env:
|
||||||
@ -53,6 +57,49 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
|
continuous-integration-pusher:
|
||||||
|
name: "Continuous Integration Pusher"
|
||||||
|
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2.0.0"
|
||||||
|
|
||||||
|
- name: "Setup NodeJS"
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: '12.x'
|
||||||
|
|
||||||
|
- name: Install Protoc
|
||||||
|
uses: arduino/setup-protoc@v1
|
||||||
|
with:
|
||||||
|
version: '3.x'
|
||||||
|
|
||||||
|
- name: "Install dependencies"
|
||||||
|
run: yarn install
|
||||||
|
working-directory: "pusher"
|
||||||
|
|
||||||
|
- name: "Install messages dependencies"
|
||||||
|
run: yarn install
|
||||||
|
working-directory: "messages"
|
||||||
|
|
||||||
|
- name: "Build proto messages"
|
||||||
|
run: yarn run proto && yarn run copy-to-pusher
|
||||||
|
working-directory: "messages"
|
||||||
|
|
||||||
|
- name: "Build"
|
||||||
|
run: yarn run tsc
|
||||||
|
working-directory: "pusher"
|
||||||
|
|
||||||
|
- name: "Lint"
|
||||||
|
run: yarn run lint
|
||||||
|
working-directory: "pusher"
|
||||||
|
|
||||||
|
- name: "Jasmine"
|
||||||
|
run: yarn test
|
||||||
|
working-directory: "pusher"
|
||||||
|
|
||||||
continuous-integration-back:
|
continuous-integration-back:
|
||||||
name: "Continuous Integration Back"
|
name: "Continuous Integration Back"
|
||||||
|
|
||||||
|
67
.github/workflows/push-to-npm.yml
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
name: Push @workadventure/iframe-api-typings to NPM
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
# Setup .npmrc file to publish to npm
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Edit tsconfig.json to add declarations
|
||||||
|
run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json"
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
|
- name: Replace version number
|
||||||
|
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
|
||||||
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
|
|
||||||
|
- name: Debug package.json
|
||||||
|
run: cat package.json
|
||||||
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
|
|
||||||
|
- name: Install Protoc
|
||||||
|
uses: arduino/setup-protoc@v1
|
||||||
|
with:
|
||||||
|
version: '3.x'
|
||||||
|
|
||||||
|
- name: "Install dependencies"
|
||||||
|
run: yarn install
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
|
- name: "Install messages dependencies"
|
||||||
|
run: yarn install
|
||||||
|
working-directory: "messages"
|
||||||
|
|
||||||
|
- name: "Build proto messages"
|
||||||
|
run: yarn run proto && yarn run copy-to-front
|
||||||
|
working-directory: "messages"
|
||||||
|
|
||||||
|
- name: "Create index.html"
|
||||||
|
run: ./templater.sh
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
|
- name: "Build"
|
||||||
|
run: yarn run build
|
||||||
|
env:
|
||||||
|
API_URL: "localhost:8080"
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
|
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
||||||
|
- name: Copy typings to package dir
|
||||||
|
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
||||||
|
|
||||||
|
- name: Install dependencies in package
|
||||||
|
run: yarn install
|
||||||
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
|
|
||||||
|
- name: Publish package
|
||||||
|
run: yarn publish
|
||||||
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
17
README.md
@ -1,4 +1,4 @@
|
|||||||
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg)
|
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
|
||||||
|
|
||||||
![WorkAdventure landscape image](README-INTRO.jpg)
|
![WorkAdventure landscape image](README-INTRO.jpg)
|
||||||
|
|
||||||
@ -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.
|
||||||
|
|
||||||
@ -27,13 +25,14 @@ docker-compose up
|
|||||||
|
|
||||||
The environment will start.
|
The environment will start.
|
||||||
|
|
||||||
You should now be able to browse to http://workadventure.localhost/ and see the application.
|
You should now be able to browse to http://play.workadventure.localhost/ and see the application.
|
||||||
|
You can view the dashboard at http://workadventure.localhost:8080/
|
||||||
|
|
||||||
Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
|
Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
|
||||||
|
|
||||||
**/etc/hosts**
|
**/etc/hosts**
|
||||||
```
|
```
|
||||||
workadventure.localhost 127.0.0.1
|
127.0.0.1 workadventure.localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
### MacOS developers, your environment with Vagrant
|
### MacOS developers, your environment with Vagrant
|
||||||
@ -101,5 +100,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,5 +1,4 @@
|
|||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import {HttpResponse} from "uWebSockets.js";
|
||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
|
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
|
@ -4,7 +4,6 @@ import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
|||||||
import { parse } from 'query-string';
|
import { parse } from 'query-string';
|
||||||
import {App} from "../Server/sifrr.server";
|
import {App} from "../Server/sifrr.server";
|
||||||
import {socketManager} from "../Services/SocketManager";
|
import {socketManager} from "../Services/SocketManager";
|
||||||
import {ServerWritableStream} from "grpc";
|
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(private App : App) {
|
constructor(private App : App) {
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
|
||||||
const URL_ROOM_STARTED = "/Floor0/floor0.json";
|
|
||||||
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;
|
||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
||||||
const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600;
|
|
||||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||||
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_ISS = process.env.JITSI_ISS || '';
|
const JITSI_ISS = process.env.JITSI_ISS || '';
|
||||||
@ -13,16 +10,14 @@ 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,
|
|
||||||
URL_ROOM_STARTED,
|
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
ADMIN_API_URL,
|
ADMIN_API_URL,
|
||||||
ADMIN_API_TOKEN,
|
ADMIN_API_TOKEN,
|
||||||
HTTP_PORT,
|
HTTP_PORT,
|
||||||
GRPC_PORT,
|
GRPC_PORT,
|
||||||
MAX_USERS_PER_ROOM,
|
|
||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
ALLOW_ARTILLERY,
|
ALLOW_ARTILLERY,
|
||||||
CPU_OVERHEAT_THRESHOLD,
|
CPU_OVERHEAT_THRESHOLD,
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
import { Group } from "./Group";
|
|
||||||
import { PointInterface } from "./Websocket/PointInterface";
|
|
||||||
import {Zone} from "_Model/Zone";
|
|
||||||
import {Movable} from "_Model/Movable";
|
|
||||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
|
||||||
import {ServerDuplexStream} from "grpc";
|
|
||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SubMessage
|
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
|
||||||
import {AdminSocket} from "../RoomManager";
|
import {AdminSocket} from "../RoomManager";
|
||||||
|
|
||||||
|
|
||||||
@ -21,16 +14,26 @@ export class Admin {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendUserJoin(uuid: string): void {
|
public sendUserJoin(uuid: string, name: string, ip: string): void {
|
||||||
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
||||||
serverToAdminClientMessage.setUseruuidjoinedroom(uuid);
|
|
||||||
|
const userJoinedRoomMessage = new UserJoinedRoomMessage();
|
||||||
|
userJoinedRoomMessage.setUuid(uuid);
|
||||||
|
userJoinedRoomMessage.setName(name);
|
||||||
|
userJoinedRoomMessage.setIpaddress(ip);
|
||||||
|
|
||||||
|
serverToAdminClientMessage.setUserjoinedroom(userJoinedRoomMessage);
|
||||||
|
|
||||||
this.socket.write(serverToAdminClientMessage);
|
this.socket.write(serverToAdminClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendUserLeft(uuid: string): void {
|
public sendUserLeft(uuid: string/*, name: string, ip: string*/): void {
|
||||||
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
||||||
serverToAdminClientMessage.setUseruuidleftroom(uuid);
|
|
||||||
|
const userLeftRoomMessage = new UserLeftRoomMessage();
|
||||||
|
userLeftRoomMessage.setUuid(uuid);
|
||||||
|
|
||||||
|
serverToAdminClientMessage.setUserleftroom(userLeftRoomMessage);
|
||||||
|
|
||||||
this.socket.write(serverToAdminClientMessage);
|
this.socket.write(serverToAdminClientMessage);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import {PositionNotifier} from "./PositionNotifier";
|
|||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import {arrayIntersect} from "../Services/ArrayHelper";
|
||||||
import {MAX_USERS_PER_ROOM} from "../Enum/EnvironmentVariable";
|
|
||||||
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
|
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
import {ZoneSocket} from "src/RoomManager";
|
import {ZoneSocket} from "src/RoomManager";
|
||||||
@ -39,12 +38,10 @@ export class GameRoom {
|
|||||||
|
|
||||||
private readonly positionNotifier: PositionNotifier;
|
private readonly positionNotifier: PositionNotifier;
|
||||||
public readonly roomId: string;
|
public readonly roomId: string;
|
||||||
public readonly anonymous: boolean;
|
|
||||||
public tags: string[];
|
|
||||||
public policyType: GameRoomPolicyTypes;
|
|
||||||
public readonly roomSlug: string;
|
public readonly roomSlug: string;
|
||||||
public readonly worldSlug: string = '';
|
public readonly worldSlug: string = '';
|
||||||
public readonly organizationSlug: string = '';
|
public readonly organizationSlug: string = '';
|
||||||
|
private versionNumber:number = 1;
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(roomId: string,
|
constructor(roomId: string,
|
||||||
@ -57,11 +54,8 @@ export class GameRoom {
|
|||||||
onLeaves: LeavesCallback)
|
onLeaves: LeavesCallback)
|
||||||
{
|
{
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.anonymous = isRoomAnonymous(roomId);
|
|
||||||
this.tags = [];
|
|
||||||
this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY;
|
|
||||||
|
|
||||||
if (this.anonymous) {
|
if (isRoomAnonymous(roomId)) {
|
||||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||||
} else {
|
} else {
|
||||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
||||||
@ -102,17 +96,26 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
const position = ProtobufUtils.toPointInterface(positionMessage);
|
const position = ProtobufUtils.toPointInterface(positionMessage);
|
||||||
|
|
||||||
const user = new User(this.nextUserId, joinRoomMessage.getUseruuid(), position, false, this.positionNotifier, socket, joinRoomMessage.getTagList(), joinRoomMessage.getName(), ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()));
|
const user = new User(this.nextUserId,
|
||||||
|
joinRoomMessage.getUseruuid(),
|
||||||
|
joinRoomMessage.getIpaddress(),
|
||||||
|
position,
|
||||||
|
false,
|
||||||
|
this.positionNotifier,
|
||||||
|
socket,
|
||||||
|
joinRoomMessage.getTagList(),
|
||||||
|
joinRoomMessage.getName(),
|
||||||
|
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()),
|
||||||
|
joinRoomMessage.getCompanion()
|
||||||
|
);
|
||||||
this.nextUserId++;
|
this.nextUserId++;
|
||||||
this.users.set(user.id, user);
|
this.users.set(user.id, user);
|
||||||
this.usersByUuid.set(user.uuid, user);
|
this.usersByUuid.set(user.uuid, user);
|
||||||
// Let's call update position to trigger the join / leave room
|
|
||||||
//this.updatePosition(socket, userPosition);
|
|
||||||
this.updateUserGroup(user);
|
this.updateUserGroup(user);
|
||||||
|
|
||||||
// Notify admins
|
// Notify admins
|
||||||
for (const admin of this.admins) {
|
for (const admin of this.admins) {
|
||||||
admin.sendUserJoin(user.uuid);
|
admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
@ -135,14 +138,10 @@ export class GameRoom {
|
|||||||
|
|
||||||
// Notify admins
|
// Notify admins
|
||||||
for (const admin of this.admins) {
|
for (const admin of this.admins) {
|
||||||
admin.sendUserLeft(user.uuid);
|
admin.sendUserLeft(user.uuid/*, user.name, user.IPAddress*/);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFull(): boolean {
|
|
||||||
return this.users.size >= MAX_USERS_PER_ROOM;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isEmpty(): boolean {
|
public isEmpty(): boolean {
|
||||||
return this.users.size === 0 && this.admins.size === 0;
|
return this.users.size === 0 && this.admins.size === 0;
|
||||||
}
|
}
|
||||||
@ -301,10 +300,6 @@ export class GameRoom {
|
|||||||
return this.itemsState;
|
return this.itemsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public canAccess(userTags: string[]): boolean {
|
|
||||||
return arrayIntersect(userTags, this.tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
||||||
return this.positionNotifier.addZoneListener(call, x, y);
|
return this.positionNotifier.addZoneListener(call, x, y);
|
||||||
}
|
}
|
||||||
@ -318,11 +313,16 @@ export class GameRoom {
|
|||||||
|
|
||||||
// Let's send all connected users
|
// Let's send all connected users
|
||||||
for (const user of this.users.values()) {
|
for (const user of this.users.values()) {
|
||||||
admin.sendUserJoin(user.uuid);
|
admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public adminLeave(admin: Admin): void {
|
public adminLeave(admin: Admin): void {
|
||||||
this.admins.delete(admin);
|
this.admins.delete(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public incrementVersion(): number {
|
||||||
|
this.versionNumber++
|
||||||
|
return this.versionNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import {Zone} from "_Model/Zone";
|
|||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
import {PositionNotifier} from "_Model/PositionNotifier";
|
||||||
import {ServerDuplexStream} from "grpc";
|
import {ServerDuplexStream} from "grpc";
|
||||||
import {BatchMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
||||||
|
|
||||||
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
||||||
@ -16,13 +16,15 @@ export class User implements Movable {
|
|||||||
public constructor(
|
public constructor(
|
||||||
public id: number,
|
public id: number,
|
||||||
public readonly uuid: string,
|
public readonly uuid: string,
|
||||||
|
public readonly IPAddress: string,
|
||||||
private position: PointInterface,
|
private position: PointInterface,
|
||||||
public silent: boolean,
|
public silent: boolean,
|
||||||
private positionNotifier: PositionNotifier,
|
private positionNotifier: PositionNotifier,
|
||||||
public readonly socket: UserSocket,
|
public readonly socket: UserSocket,
|
||||||
public readonly tags: string[],
|
public readonly tags: string[],
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly characterLayers: CharacterLayer[]
|
public readonly characterLayers: CharacterLayer[],
|
||||||
|
public readonly companion?: CompanionMessage
|
||||||
) {
|
) {
|
||||||
this.listenedZones = new Set<Zone>();
|
this.listenedZones = new Set<Zone>();
|
||||||
|
|
||||||
|
@ -2,25 +2,23 @@ import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
|
|||||||
import {
|
import {
|
||||||
AdminGlobalMessage,
|
AdminGlobalMessage,
|
||||||
AdminMessage,
|
AdminMessage,
|
||||||
AdminPusherToBackMessage, BanMessage,
|
AdminPusherToBackMessage,
|
||||||
ClientToServerMessage, EmptyMessage,
|
AdminRoomMessage,
|
||||||
|
BanMessage,
|
||||||
|
EmptyMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
QueryJitsiJwtMessage,
|
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
|
||||||
ReportPlayerMessage,
|
|
||||||
RoomJoinedMessage,
|
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
ViewportMessage,
|
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
|
||||||
WebRtcSignalToServerMessage,
|
|
||||||
ZoneMessage
|
ZoneMessage
|
||||||
} from "./Messages/generated/messages_pb";
|
} from "./Messages/generated/messages_pb";
|
||||||
import grpc, {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
||||||
import {Empty} from "google-protobuf/google/protobuf/empty_pb";
|
|
||||||
import {socketManager} from "./Services/SocketManager";
|
import {socketManager} from "./Services/SocketManager";
|
||||||
import {emitError} from "./Services/MessageHelpers";
|
import {emitError} from "./Services/MessageHelpers";
|
||||||
import {User, UserSocket} from "./Model/User";
|
import {User, UserSocket} from "./Model/User";
|
||||||
@ -45,8 +43,13 @@ const roomManager: IRoomManagerServer = {
|
|||||||
if (room === null || user === null) {
|
if (room === null || user === null) {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
||||||
|
if (call.writable) {
|
||||||
room = gameRoom;
|
room = gameRoom;
|
||||||
user = myUser;
|
user = myUser;
|
||||||
|
} else {
|
||||||
|
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
||||||
|
socketManager.leaveRoom(gameRoom, myUser);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
||||||
@ -54,12 +57,8 @@ const roomManager: IRoomManagerServer = {
|
|||||||
} else {
|
} else {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
throw new Error('Cannot call JoinRoomMessage twice!');
|
throw new Error('Cannot call JoinRoomMessage twice!');
|
||||||
/*} else if (message.hasViewportmessage()) {
|
|
||||||
socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);*/
|
|
||||||
} else if (message.hasUsermovesmessage()) {
|
} else if (message.hasUsermovesmessage()) {
|
||||||
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
||||||
/*} else if (message.hasSetplayerdetailsmessage()) {
|
|
||||||
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);*/
|
|
||||||
} else if (message.hasSilentmessage()) {
|
} else if (message.hasSilentmessage()) {
|
||||||
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
||||||
} else if (message.hasItemeventmessage()) {
|
} else if (message.hasItemeventmessage()) {
|
||||||
@ -70,10 +69,18 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
} else if (message.hasPlayglobalmessage()) {
|
||||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
/*} else if (message.hasReportplayermessage()){
|
|
||||||
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/
|
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()){
|
||||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||||
|
}else if (message.hasSendusermessage()) {
|
||||||
|
const sendUserMessage = message.getSendusermessage();
|
||||||
|
if(sendUserMessage !== undefined) {
|
||||||
|
socketManager.handlerSendUserMessage(user, sendUserMessage);
|
||||||
|
}
|
||||||
|
}else if (message.hasBanusermessage()) {
|
||||||
|
const banUserMessage = message.getBanusermessage();
|
||||||
|
if(banUserMessage !== undefined) {
|
||||||
|
socketManager.handlerBanUserMessage(room, user, banUserMessage);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unhandled message type');
|
throw new Error('Unhandled message type');
|
||||||
}
|
}
|
||||||
@ -113,9 +120,6 @@ const roomManager: IRoomManagerServer = {
|
|||||||
call.end();
|
call.end();
|
||||||
})
|
})
|
||||||
|
|
||||||
/*call.on('finish', () => {
|
|
||||||
debug('listenZone finish');
|
|
||||||
})*/
|
|
||||||
call.on('close', () => {
|
call.on('close', () => {
|
||||||
debug('listenZone connection closed');
|
debug('listenZone connection closed');
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
@ -143,26 +147,6 @@ const roomManager: IRoomManagerServer = {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
/*if (message.hasJoinroommessage()) {
|
|
||||||
throw new Error('Cannot call JoinRoomMessage twice!');
|
|
||||||
} else if (message.hasUsermovesmessage()) {
|
|
||||||
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
|
||||||
} else if (message.hasSilentmessage()) {
|
|
||||||
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
|
||||||
} else if (message.hasItemeventmessage()) {
|
|
||||||
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
|
|
||||||
} else if (message.hasWebrtcsignaltoservermessage()) {
|
|
||||||
socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
|
||||||
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
|
||||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
|
||||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
|
||||||
} else {
|
|
||||||
throw new Error('Unhandled message type');
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emitError(call, e);
|
emitError(call, e);
|
||||||
@ -196,9 +180,21 @@ const roomManager: IRoomManagerServer = {
|
|||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
// FIXME Work in progress
|
||||||
|
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage());
|
||||||
|
|
||||||
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid());
|
callback(null, new EmptyMessage());
|
||||||
|
},
|
||||||
|
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
||||||
|
callback(null, new EmptyMessage());
|
||||||
|
},
|
||||||
|
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
||||||
|
callback(null, new EmptyMessage());
|
||||||
|
},
|
||||||
|
sendRefreshRoomPrompt(call: ServerUnaryCall<RefreshRoomPromptMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
|
||||||
import Axios from "axios";
|
|
||||||
import {v4} from "uuid";
|
|
||||||
|
|
||||||
export interface AdminApiData {
|
|
||||||
organizationSlug: string
|
|
||||||
worldSlug: string
|
|
||||||
roomSlug: string
|
|
||||||
mapUrlStart: string
|
|
||||||
tags: string[]
|
|
||||||
policy_type: number
|
|
||||||
userUuid: string
|
|
||||||
messages?: unknown[],
|
|
||||||
textures: CharacterTexture[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CharacterTexture {
|
|
||||||
id: number,
|
|
||||||
level: number,
|
|
||||||
url: string,
|
|
||||||
rights: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FetchMemberDataByUuidResponse {
|
|
||||||
uuid: string;
|
|
||||||
tags: string[];
|
|
||||||
textures: CharacterTexture[];
|
|
||||||
messages: unknown[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class AdminApi {
|
|
||||||
|
|
||||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
|
||||||
organizationSlug,
|
|
||||||
worldSlug
|
|
||||||
};
|
|
||||||
|
|
||||||
if (roomSlug) {
|
|
||||||
params.roomSlug = roomSlug;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await Axios.get(ADMIN_API_URL + '/api/map',
|
|
||||||
{
|
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
|
|
||||||
params
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
|
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
} catch (e) {
|
|
||||||
if (e?.response?.status == 404) {
|
|
||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
|
||||||
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
|
|
||||||
return {
|
|
||||||
uuid: v4(),
|
|
||||||
tags: [],
|
|
||||||
textures: [],
|
|
||||||
messages: [],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
|
||||||
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
|
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
|
||||||
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
|
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
|
|
||||||
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
|
||||||
reportedUserUuid,
|
|
||||||
reportedUserComment,
|
|
||||||
reporterUserUuid,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const adminApi = new AdminApi();
|
|
@ -1,24 +1,15 @@
|
|||||||
import {GameRoom} from "../Model/GameRoom";
|
import {GameRoom} from "../Model/GameRoom";
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
|
||||||
import {
|
import {
|
||||||
GroupDeleteMessage,
|
|
||||||
GroupUpdateMessage,
|
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
ItemStateMessage,
|
ItemStateMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
PointMessage,
|
PointMessage,
|
||||||
PositionMessage,
|
|
||||||
RoomJoinedMessage,
|
RoomJoinedMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SetPlayerDetailsMessage,
|
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
SubMessage,
|
SubMessage,
|
||||||
ReportPlayerMessage,
|
|
||||||
UserJoinedMessage,
|
|
||||||
UserLeftMessage,
|
|
||||||
UserMovedMessage,
|
UserMovedMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
ViewportMessage,
|
|
||||||
WebRtcDisconnectMessage,
|
WebRtcDisconnectMessage,
|
||||||
WebRtcSignalToClientMessage,
|
WebRtcSignalToClientMessage,
|
||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage,
|
||||||
@ -28,47 +19,47 @@ import {
|
|||||||
SendUserMessage,
|
SendUserMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
Zone as ProtoZone,
|
Zone as ProtoZone,
|
||||||
BatchMessage,
|
|
||||||
BatchToPusherMessage,
|
BatchToPusherMessage,
|
||||||
SubToPusherMessage,
|
SubToPusherMessage,
|
||||||
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, AdminMessage, BanMessage
|
UserJoinedZoneMessage,
|
||||||
|
GroupUpdateZoneMessage,
|
||||||
|
GroupLeftZoneMessage,
|
||||||
|
WorldFullWarningMessage,
|
||||||
|
UserLeftZoneMessage,
|
||||||
|
BanUserMessage, RefreshRoomMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {User, UserSocket} from "../Model/User";
|
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 {ADMIN_API_URL, 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, FetchMemberDataByUuidResponse} from "./AdminApi";
|
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
||||||
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
||||||
import {gaugeManager} from "./GaugeManager";
|
import {gaugeManager} from "./GaugeManager";
|
||||||
import {AdminSocket, ZoneSocket} from "../RoomManager";
|
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');
|
||||||
|
|
||||||
interface AdminSocketRoomsList {
|
|
||||||
[index: string]: number;
|
|
||||||
}
|
|
||||||
interface AdminSocketUsersList {
|
|
||||||
[index: string]: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminSocketData {
|
|
||||||
rooms: AdminSocketRoomsList,
|
|
||||||
users: AdminSocketUsersList,
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
||||||
// TODO: should we batch those every 100ms?
|
// TODO: should we batch those every 100ms?
|
||||||
const batchMessage = new BatchToPusherMessage();
|
const batchMessage = new BatchToPusherMessage();
|
||||||
batchMessage.addPayload(subMessage);
|
batchMessage.addPayload(subMessage);
|
||||||
|
|
||||||
|
|
||||||
socket.write(batchMessage);
|
socket.write(batchMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,68 +75,20 @@ export class SocketManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*getAdminSocketDataFor(roomId:string): AdminSocketData {
|
|
||||||
const data:AdminSocketData = {
|
|
||||||
rooms: {},
|
|
||||||
users: {},
|
|
||||||
}
|
|
||||||
const room = this.rooms.get(roomId);
|
|
||||||
if (room === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const users = room.getUsers();
|
|
||||||
data.rooms[roomId] = users.size;
|
|
||||||
users.forEach(user => {
|
|
||||||
data.users[user.uuid] = true
|
|
||||||
})
|
|
||||||
return data;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
|
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
|
||||||
/*const positionMessage = joinRoomMessage.getPositionmessage();
|
|
||||||
if (positionMessage === undefined) {
|
|
||||||
// TODO: send error message?
|
|
||||||
throw new Error('Empty pointMessage found in JoinRoomMessage');
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//const position = ProtobufUtils.toPointInterface(positionMessage);
|
|
||||||
//const viewport = client.viewport;
|
|
||||||
|
|
||||||
//this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
|
|
||||||
|
|
||||||
//join new previous room
|
//join new previous room
|
||||||
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
||||||
|
|
||||||
//const things = room.setViewport(client, viewport);
|
if (!socket.writable) {
|
||||||
|
console.warn('Socket was aborted');
|
||||||
|
return {
|
||||||
|
room,
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
const roomJoinedMessage = new RoomJoinedMessage();
|
const roomJoinedMessage = new RoomJoinedMessage();
|
||||||
|
|
||||||
/*for (const thing of things) {
|
|
||||||
if (thing instanceof User) {
|
|
||||||
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
|
|
||||||
if (player === undefined) {
|
|
||||||
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userJoinedMessage = new UserJoinedMessage();
|
|
||||||
userJoinedMessage.setUserid(thing.id);
|
|
||||||
userJoinedMessage.setName(player.name);
|
|
||||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers));
|
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
|
|
||||||
|
|
||||||
roomJoinedMessage.addUser(userJoinedMessage);
|
|
||||||
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
||||||
} else if (thing instanceof Group) {
|
|
||||||
const groupUpdateMessage = new GroupUpdateMessage();
|
|
||||||
groupUpdateMessage.setGroupid(thing.getId());
|
|
||||||
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
|
|
||||||
|
|
||||||
roomJoinedMessage.addGroup(groupUpdateMessage);
|
|
||||||
} else {
|
|
||||||
console.error("Unexpected type for Movable returned by setViewport");
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (const [itemId, item] of room.getItemsState().entries()) {
|
for (const [itemId, item] of room.getItemsState().entries()) {
|
||||||
const itemStateMessage = new ItemStateMessage();
|
const itemStateMessage = new ItemStateMessage();
|
||||||
@ -159,9 +102,6 @@ export class SocketManager {
|
|||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
||||||
|
|
||||||
//user.socket.write(serverToClientMessage);
|
|
||||||
console.log('SENDING MESSAGE roomJoinedMessage');
|
|
||||||
socket.write(serverToClientMessage);
|
socket.write(serverToClientMessage);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -169,13 +109,6 @@ export class SocketManager {
|
|||||||
user
|
user
|
||||||
};
|
};
|
||||||
|
|
||||||
/*const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
|
||||||
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
||||||
@ -196,14 +129,6 @@ export class SocketManager {
|
|||||||
throw new Error('Viewport not found in message');
|
throw new Error('Viewport not found in message');
|
||||||
}
|
}
|
||||||
|
|
||||||
// sending to all clients in room except sender
|
|
||||||
/*client.position = {
|
|
||||||
x: position.x,
|
|
||||||
y: position.y,
|
|
||||||
direction,
|
|
||||||
moving: position.moving,
|
|
||||||
};
|
|
||||||
client.viewport = viewport;*/
|
|
||||||
|
|
||||||
// update position in the world
|
// update position in the world
|
||||||
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
||||||
@ -258,21 +183,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle this message in pusher
|
|
||||||
/*async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
|
||||||
try {
|
|
||||||
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
|
||||||
if (!reportedSocket) {
|
|
||||||
throw 'reported socket user not found';
|
|
||||||
}
|
|
||||||
//TODO report user on admin application
|
|
||||||
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "handleReportMessage"');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
|
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
|
||||||
//send only at user
|
//send only at user
|
||||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||||
@ -284,6 +194,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);
|
||||||
@ -304,6 +220,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);
|
||||||
@ -324,8 +246,6 @@ export class SocketManager {
|
|||||||
debug('Room is empty. Deleting room "%s"', room.roomId);
|
debug('Room is empty. Deleting room "%s"', room.roomId);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
//delete Client.roomId;
|
|
||||||
//this.sockets.delete(Client.userId);
|
|
||||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
||||||
console.log('A user left');
|
console.log('A user left');
|
||||||
}
|
}
|
||||||
@ -345,11 +265,6 @@ export class SocketManager {
|
|||||||
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
||||||
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
|
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
|
||||||
);
|
);
|
||||||
if (!world.anonymous) {
|
|
||||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
|
||||||
world.tags = data.tags
|
|
||||||
world.policyType = Number(data.policy_type)
|
|
||||||
}
|
|
||||||
gaugeManager.incNbRoomGauge();
|
gaugeManager.incNbRoomGauge();
|
||||||
this.rooms.set(roomId, world);
|
this.rooms.set(roomId, world);
|
||||||
}
|
}
|
||||||
@ -360,20 +275,14 @@ export class SocketManager {
|
|||||||
|
|
||||||
const roomId = joinRoomMessage.getRoomid();
|
const roomId = joinRoomMessage.getRoomid();
|
||||||
|
|
||||||
const world = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
|
|
||||||
// Dispatch groups position to newly connected user
|
|
||||||
/*world.getGroups().forEach((group: Group) => {
|
|
||||||
this.emitCreateUpdateGroupEvent(socket, group);
|
|
||||||
});*/
|
|
||||||
|
|
||||||
//join world
|
//join world
|
||||||
const user = world.join(socket, joinRoomMessage);
|
const user = room.join(socket, joinRoomMessage);
|
||||||
|
|
||||||
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
||||||
//console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
|
|
||||||
console.log(new Date().toISOString() + ' A user joined');
|
console.log(new Date().toISOString() + ' A user joined');
|
||||||
return {room: world, user};
|
return {room, user};
|
||||||
}
|
}
|
||||||
|
|
||||||
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
||||||
@ -387,6 +296,7 @@ export class SocketManager {
|
|||||||
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||||
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||||
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
|
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
|
||||||
|
userJoinedZoneMessage.setCompanion(thing.companion);
|
||||||
|
|
||||||
const subMessage = new SubToPusherMessage();
|
const subMessage = new SubToPusherMessage();
|
||||||
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
|
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
|
||||||
@ -481,10 +391,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private joinWebRtcRoom(user: User, group: Group) {
|
private joinWebRtcRoom(user: User, group: Group) {
|
||||||
/*const roomId: string = "webrtcroom"+group.getId();
|
|
||||||
if (user.socket.webRtcRoomId === roomId) {
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (const otherUser of group.getUsers()) {
|
for (const otherUser of group.getUsers()) {
|
||||||
if (user === otherUser) {
|
if (user === otherUser) {
|
||||||
@ -496,6 +402,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);
|
||||||
@ -509,6 +420,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);
|
||||||
@ -521,6 +437,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
|
||||||
@ -626,31 +561,31 @@ export class SocketManager {
|
|||||||
user.socket.write(serverToClientMessage);
|
user.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){
|
||||||
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
|
const sendUserMessage = new SendUserMessage();
|
||||||
*/
|
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
||||||
static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] {
|
sendUserMessage.setType(sendUserMessageToSend.getType());
|
||||||
const characterLayerObjs: CharacterLayer[] = [];
|
|
||||||
for (const characterLayer of characterLayers) {
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
if (characterLayer.startsWith('customCharacterTexture')) {
|
serverToClientMessage.setSendusermessage(sendUserMessage);
|
||||||
const customCharacterLayerId: number = +characterLayer.substr(22);
|
user.socket.write(serverToClientMessage);
|
||||||
for (const memberTexture of memberTextures) {
|
|
||||||
if (memberTexture.id == customCharacterLayerId) {
|
|
||||||
characterLayerObjs.push({
|
|
||||||
name: characterLayer,
|
|
||||||
url: memberTexture.url
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){
|
||||||
characterLayerObjs.push({
|
const banUserMessage = new BanUserMessage();
|
||||||
name: characterLayer,
|
banUserMessage.setMessage(banUserMessageToSend.getMessage());
|
||||||
url: undefined
|
banUserMessage.setType(banUserMessageToSend.getType());
|
||||||
})
|
|
||||||
}
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
}
|
serverToClientMessage.setSendusermessage(banUserMessage);
|
||||||
return characterLayerObjs;
|
user.socket.write(serverToClientMessage);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// Let's leave the room now.
|
||||||
|
room.leave(user);
|
||||||
|
// Let's close the connection when the user is banned.
|
||||||
|
user.socket.end();
|
||||||
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
||||||
@ -671,6 +606,7 @@ export class SocketManager {
|
|||||||
userJoinedMessage.setName(thing.name);
|
userJoinedMessage.setName(thing.name);
|
||||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||||
|
userJoinedMessage.setCompanion(thing.companion);
|
||||||
|
|
||||||
const subMessage = new SubToPusherMessage();
|
const subMessage = new SubToPusherMessage();
|
||||||
subMessage.setUserjoinedzonemessage(userJoinedMessage);
|
subMessage.setUserjoinedzonemessage(userJoinedMessage);
|
||||||
@ -706,11 +642,6 @@ export class SocketManager {
|
|||||||
public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise<GameRoom> {
|
public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise<GameRoom> {
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
|
|
||||||
// Dispatch groups position to newly connected user
|
|
||||||
/*world.getGroups().forEach((group: Group) => {
|
|
||||||
this.emitCreateUpdateGroupEvent(socket, group);
|
|
||||||
});*/
|
|
||||||
|
|
||||||
room.adminJoin(admin);
|
room.adminJoin(admin);
|
||||||
|
|
||||||
return room;
|
return room;
|
||||||
@ -740,15 +671,15 @@ export class SocketManager {
|
|||||||
|
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType('ban');
|
sendUserMessage.setType('ban'); //todo: is the type correct?
|
||||||
|
|
||||||
const subToPusherMessage = new SubToPusherMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
subToPusherMessage.setSendusermessage(sendUserMessage);
|
serverToClientMessage.setSendusermessage(sendUserMessage);
|
||||||
|
|
||||||
recipient.socket.write(subToPusherMessage);
|
recipient.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public banUser(roomId: string, recipientUuid: string): void {
|
public banUser(roomId: string, recipientUuid: string, message: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||||
@ -764,17 +695,75 @@ export class SocketManager {
|
|||||||
// Let's leave the room now.
|
// Let's leave the room now.
|
||||||
room.leave(recipient);
|
room.leave(recipient);
|
||||||
|
|
||||||
const sendUserMessage = new SendUserMessage();
|
const banUserMessage = new BanUserMessage();
|
||||||
sendUserMessage.setType('banned');
|
banUserMessage.setMessage(message);
|
||||||
|
banUserMessage.setType('banned');
|
||||||
|
|
||||||
const subToPusherMessage = new SubToPusherMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
subToPusherMessage.setSendusermessage(sendUserMessage);
|
serverToClientMessage.setBanusermessage(banUserMessage);
|
||||||
|
|
||||||
recipient.socket.write(subToPusherMessage);
|
|
||||||
|
|
||||||
// Let's close the connection when the user is banned.
|
// Let's close the connection when the user is banned.
|
||||||
|
recipient.socket.write(serverToClientMessage);
|
||||||
recipient.socket.end();
|
recipient.socket.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sendAdminRoomMessage(roomId: string, message: string) {
|
||||||
|
const room = this.rooms.get(roomId);
|
||||||
|
if (!room) {
|
||||||
|
//todo: this should cause the http call to return a 500
|
||||||
|
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.getUsers().forEach((recipient) => {
|
||||||
|
const sendUserMessage = new SendUserMessage();
|
||||||
|
sendUserMessage.setMessage(message);
|
||||||
|
sendUserMessage.setType('message');
|
||||||
|
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setSendusermessage(sendUserMessage);
|
||||||
|
|
||||||
|
recipient.socket.write(clientMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchWorlFullWarning(roomId: string,): void {
|
||||||
|
const room = this.rooms.get(roomId);
|
||||||
|
if (!room) {
|
||||||
|
//todo: this should cause the http call to return a 500
|
||||||
|
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.getUsers().forEach((recipient) => {
|
||||||
|
const worldFullMessage = new WorldFullWarningMessage();
|
||||||
|
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setWorldfullwarningmessage(worldFullMessage);
|
||||||
|
|
||||||
|
recipient.socket.write(clientMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchRoomRefresh(roomId: string,): void {
|
||||||
|
const room = this.rooms.get(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionNumber = room.incrementVersion();
|
||||||
|
room.getUsers().forEach((recipient) => {
|
||||||
|
const worldFullMessage = new RefreshRoomMessage();
|
||||||
|
worldFullMessage.setRoomid(roomId)
|
||||||
|
worldFullMessage.setVersionnumber(versionNumber)
|
||||||
|
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setRefreshroommessage(worldFullMessage);
|
||||||
|
|
||||||
|
recipient.socket.write(clientMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -26,6 +26,7 @@ function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMess
|
|||||||
positionMessage.setMoving(false);
|
positionMessage.setMoving(false);
|
||||||
const joinRoomMessage = new JoinRoomMessage();
|
const joinRoomMessage = new JoinRoomMessage();
|
||||||
joinRoomMessage.setUseruuid('1');
|
joinRoomMessage.setUseruuid('1');
|
||||||
|
joinRoomMessage.setIpaddress('10.0.0.2');
|
||||||
joinRoomMessage.setName('foo');
|
joinRoomMessage.setName('foo');
|
||||||
joinRoomMessage.setRoomid('_/global/test.json');
|
joinRoomMessage.setRoomid('_/global/test.json');
|
||||||
joinRoomMessage.setPositionmessage(positionMessage);
|
joinRoomMessage.setPositionmessage(positionMessage);
|
||||||
|
@ -25,14 +25,14 @@ describe("PositionNotifier", () => {
|
|||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const user1 = new User(1, 'test', {
|
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||||
x: 500,
|
x: 500,
|
||||||
y: 500,
|
y: 500,
|
||||||
moving: false,
|
moving: false,
|
||||||
direction: 'down'
|
direction: 'down'
|
||||||
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', {
|
const user2 = new User(2, 'test', '10.0.0.2', {
|
||||||
x: -9999,
|
x: -9999,
|
||||||
y: -9999,
|
y: -9999,
|
||||||
moving: false,
|
moving: false,
|
||||||
@ -100,14 +100,14 @@ describe("PositionNotifier", () => {
|
|||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const user1 = new User(1, 'test', {
|
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||||
x: 500,
|
x: 500,
|
||||||
y: 500,
|
y: 500,
|
||||||
moving: false,
|
moving: false,
|
||||||
direction: 'down'
|
direction: 'down'
|
||||||
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', {
|
const user2 = new User(2, 'test', '10.0.0.2', {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
moving: false,
|
moving: false,
|
||||||
|
@ -2833,9 +2833,9 @@ xtend@^4.0.0:
|
|||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
y18n@^3.2.0:
|
y18n@^3.2.0:
|
||||||
version "3.2.1"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||||
|
|
||||||
yallist@^3.0.0, yallist@^3.0.3:
|
yallist@^3.0.0, yallist@^3.0.3:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
|
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=${ACME_EMAIL}
|
||||||
|
- --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"
|
||||||
|
PUSHER_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://admin."+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,9 +22,13 @@
|
|||||||
"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,
|
||||||
} + if adminUrl != null then {
|
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||||
|
} + (if adminUrl != null then {
|
||||||
"ADMIN_API_URL": adminUrl,
|
"ADMIN_API_URL": adminUrl,
|
||||||
} else {}
|
} else {}) + if namespace != "master" then {
|
||||||
|
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||||
|
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"back2": {
|
"back2": {
|
||||||
"image": "thecodingmachine/workadventure-back:"+tag,
|
"image": "thecodingmachine/workadventure-back:"+tag,
|
||||||
@ -39,9 +44,13 @@
|
|||||||
"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,
|
||||||
} + if adminUrl != null then {
|
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||||
|
} + (if adminUrl != null then {
|
||||||
"ADMIN_API_URL": adminUrl,
|
"ADMIN_API_URL": adminUrl,
|
||||||
} else {}
|
} else {}) + if namespace != "master" then {
|
||||||
|
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||||
|
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"pusher": {
|
"pusher": {
|
||||||
"replicas": 2,
|
"replicas": 2,
|
||||||
@ -58,9 +67,12 @@
|
|||||||
"JITSI_URL": env.JITSI_URL,
|
"JITSI_URL": env.JITSI_URL,
|
||||||
"API_URL": "back1:50051,back2:50051",
|
"API_URL": "back1:50051,back2:50051",
|
||||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||||
} + if adminUrl != null then {
|
} + (if adminUrl != null then {
|
||||||
"ADMIN_API_URL": adminUrl,
|
"ADMIN_API_URL": adminUrl,
|
||||||
} else {}
|
} else {}) + if namespace != "master" then {
|
||||||
|
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||||
|
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"front": {
|
"front": {
|
||||||
"image": "thecodingmachine/workadventure-front:"+tag,
|
"image": "thecodingmachine/workadventure-front:"+tag,
|
||||||
@ -70,15 +82,15 @@
|
|||||||
},
|
},
|
||||||
"ports": [80],
|
"ports": [80],
|
||||||
"env": {
|
"env": {
|
||||||
"API_URL": "pusher."+url,
|
"PUSHER_URL": "//pusher."+url,
|
||||||
"UPLOADER_URL": "uploader."+url,
|
"UPLOADER_URL": "//uploader."+url,
|
||||||
"ADMIN_URL": "admin."+url,
|
"ADMIN_URL": "//"+url,
|
||||||
"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_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
||||||
"TURN_USER": "workadventure",
|
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
||||||
"TURN_PASSWORD": "WorkAdventure123",
|
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
|
||||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false"
|
//"GA_TRACKING_ID": "UA-10196481-11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uploader": {
|
"uploader": {
|
||||||
@ -100,17 +112,6 @@
|
|||||||
},
|
},
|
||||||
"ports": [80]
|
"ports": [80]
|
||||||
},
|
},
|
||||||
"website": {
|
|
||||||
"image": "thecodingmachine/workadventure-website:"+tag,
|
|
||||||
"host": {
|
|
||||||
"url": url,
|
|
||||||
"https": "enable"
|
|
||||||
},
|
|
||||||
"ports": [80],
|
|
||||||
"env": {
|
|
||||||
"GAME_URL": "https://play."+url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"https": {
|
"https": {
|
||||||
|
@ -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
|
|
207
docker-compose.single-domain.yaml
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
reverse-proxy:
|
||||||
|
image: traefik:v2.0
|
||||||
|
command:
|
||||||
|
- --api.insecure=true
|
||||||
|
- --providers.docker
|
||||||
|
- --entryPoints.web.address=:80
|
||||||
|
- --entryPoints.websecure.address=:443
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
# The Web UI (enabled by --api.insecure=true)
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
- back
|
||||||
|
- front
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
front:
|
||||||
|
image: thecodingmachine/nodejs:14
|
||||||
|
environment:
|
||||||
|
DEBUG_MODE: "$DEBUG_MODE"
|
||||||
|
JITSI_URL: $JITSI_URL
|
||||||
|
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
NODE_ENV: development
|
||||||
|
PUSHER_URL: /pusher
|
||||||
|
UPLOADER_URL: /uploader
|
||||||
|
ADMIN_URL: /admin
|
||||||
|
MAPS_URL: /maps
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||||
|
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||||
|
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||||
|
TURN_USER: ""
|
||||||
|
TURN_PASSWORD: ""
|
||||||
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
|
command: yarn run start
|
||||||
|
volumes:
|
||||||
|
- ./front:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.front.rule=PathPrefix(`/`)"
|
||||||
|
- "traefik.http.routers.front.entryPoints=web,traefik"
|
||||||
|
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.front-ssl.rule=PathPrefix(`/`)"
|
||||||
|
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.front-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.front-ssl.service=front"
|
||||||
|
|
||||||
|
pusher:
|
||||||
|
image: thecodingmachine/nodejs:12
|
||||||
|
command: yarn dev
|
||||||
|
#command: yarn run prod
|
||||||
|
#command: yarn run profile
|
||||||
|
environment:
|
||||||
|
DEBUG: "*"
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
|
SECRET_KEY: yourSecretKey
|
||||||
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
|
API_URL: back:50051
|
||||||
|
JITSI_URL: $JITSI_URL
|
||||||
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
volumes:
|
||||||
|
- ./pusher:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher"
|
||||||
|
- "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)"
|
||||||
|
- "traefik.http.routers.pusher.middlewares=strip-pusher-prefix@docker"
|
||||||
|
- "traefik.http.routers.pusher.entryPoints=web"
|
||||||
|
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.pusher-ssl.rule=PathPrefix(`/pusher`)"
|
||||||
|
- "traefik.http.routers.pusher-ssl.middlewares=strip-pusher-prefix@docker"
|
||||||
|
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.pusher-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.pusher-ssl.service=pusher"
|
||||||
|
|
||||||
|
maps:
|
||||||
|
image: thecodingmachine/nodejs:12-apache
|
||||||
|
environment:
|
||||||
|
DEBUG_MODE: "$DEBUG_MODE"
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
NODE_ENV: development
|
||||||
|
#APACHE_DOCUMENT_ROOT: dist/
|
||||||
|
#APACHE_EXTENSIONS: headers
|
||||||
|
#APACHE_EXTENSION_HEADERS: 1
|
||||||
|
STARTUP_COMMAND_0: sudo a2enmod headers
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
STARTUP_COMMAND_2: yarn run dev &
|
||||||
|
volumes:
|
||||||
|
- ./maps:/var/www/html
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-maps-prefix.stripprefix.prefixes=/maps"
|
||||||
|
- "traefik.http.routers.maps.rule=PathPrefix(`/maps`)"
|
||||||
|
- "traefik.http.routers.maps.middlewares=strip-maps-prefix@docker"
|
||||||
|
- "traefik.http.routers.maps.entryPoints=web,traefik"
|
||||||
|
- "traefik.http.services.maps.loadbalancer.server.port=80"
|
||||||
|
- "traefik.http.routers.maps-ssl.rule=PathPrefix(`/maps`)"
|
||||||
|
- "traefik.http.routers.maps-ssl.middlewares=strip-maps-prefix@docker"
|
||||||
|
- "traefik.http.routers.maps-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.maps-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.maps-ssl.service=maps"
|
||||||
|
|
||||||
|
back:
|
||||||
|
image: thecodingmachine/nodejs:12
|
||||||
|
command: yarn dev
|
||||||
|
#command: yarn run profile
|
||||||
|
environment:
|
||||||
|
DEBUG: "*"
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
SECRET_KEY: yourSecretKey
|
||||||
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
|
ALLOW_ARTILLERY: "true"
|
||||||
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
|
JITSI_URL: $JITSI_URL
|
||||||
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
volumes:
|
||||||
|
- ./back:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
|
||||||
|
- "traefik.http.routers.back.rule=PathPrefix(`/api`)"
|
||||||
|
- "traefik.http.routers.back.middlewares=strip-api-prefix@docker"
|
||||||
|
- "traefik.http.routers.back.entryPoints=web"
|
||||||
|
- "traefik.http.services.back.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.back-ssl.rule=PathPrefix(`/api`)"
|
||||||
|
- "traefik.http.routers.back-ssl.middlewares=strip-api-prefix@docker"
|
||||||
|
- "traefik.http.routers.back-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.back-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.back-ssl.service=back"
|
||||||
|
|
||||||
|
uploader:
|
||||||
|
image: thecodingmachine/nodejs:12
|
||||||
|
command: yarn dev
|
||||||
|
#command: yarn run profile
|
||||||
|
environment:
|
||||||
|
DEBUG: "*"
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
volumes:
|
||||||
|
- ./uploader:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-uploader-prefix.stripprefix.prefixes=/uploader"
|
||||||
|
- "traefik.http.routers.uploader.rule=PathPrefix(`/uploader`)"
|
||||||
|
- "traefik.http.routers.uploader.middlewares=strip-uploader-prefix@docker"
|
||||||
|
- "traefik.http.routers.uploader.entryPoints=web"
|
||||||
|
- "traefik.http.services.uploader.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.uploader-ssl.rule=PathPrefix(`/uploader`)"
|
||||||
|
- "traefik.http.routers.uploader-ssl.middlewares=strip-uploader-prefix@docker"
|
||||||
|
- "traefik.http.routers.uploader-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.uploader-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.uploader-ssl.service=uploader"
|
||||||
|
|
||||||
|
website:
|
||||||
|
image: thecodingmachine/nodejs:12-apache
|
||||||
|
environment:
|
||||||
|
STARTUP_COMMAND_1: npm install
|
||||||
|
STARTUP_COMMAND_2: npm run watch &
|
||||||
|
APACHE_DOCUMENT_ROOT: dist/
|
||||||
|
volumes:
|
||||||
|
- ./website:/var/www/html
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
|
||||||
|
- "traefik.http.routers.website.entryPoints=web"
|
||||||
|
- "traefik.http.services.website.loadbalancer.server.port=80"
|
||||||
|
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
|
||||||
|
- "traefik.http.routers.website-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.website-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.website-ssl.service=website"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
#image: thecodingmachine/nodejs:14
|
||||||
|
image: thecodingmachine/workadventure-back-base:latest
|
||||||
|
environment:
|
||||||
|
#STARTUP_COMMAND_0: sudo apt-get install -y inotify-tools
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
STARTUP_COMMAND_2: yarn run proto:watch
|
||||||
|
volumes:
|
||||||
|
- ./messages:/usr/src/app
|
||||||
|
- ./back:/usr/src/back
|
||||||
|
- ./front:/usr/src/front
|
||||||
|
- ./pusher:/usr/src/pusher
|
||||||
|
|
||||||
|
# coturn:
|
||||||
|
# image: coturn/coturn:4.5.2
|
||||||
|
# command:
|
||||||
|
# - turnserver
|
||||||
|
# #- -c=/etc/coturn/turnserver.conf
|
||||||
|
# - --log-file=stdout
|
||||||
|
# - --external-ip=$$(detect-external-ip)
|
||||||
|
# - --listening-port=3478
|
||||||
|
# - --min-port=10000
|
||||||
|
# - --max-port=10010
|
||||||
|
# - --tls-listening-port=5349
|
||||||
|
# - --listening-ip=0.0.0.0
|
||||||
|
# - --realm=localhost
|
||||||
|
# - --server-name=localhost
|
||||||
|
# - --lt-cred-mech
|
||||||
|
# # Enable Coturn "REST API" to validate temporary passwords.
|
||||||
|
# #- --use-auth-secret
|
||||||
|
# #- --static-auth-secret=SomeStaticAuthSecret
|
||||||
|
# #- --userdb=/var/lib/turn/turndb
|
||||||
|
# - --user=workadventure:WorkAdventure123
|
||||||
|
# # use real-valid certificate/privatekey files
|
||||||
|
# #- --cert=/root/letsencrypt/fullchain.pem
|
||||||
|
# #- --pkey=/root/letsencrypt/privkey.pem
|
||||||
|
# network_mode: host
|
@ -26,19 +26,24 @@ services:
|
|||||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||||
HOST: "0.0.0.0"
|
HOST: "0.0.0.0"
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
API_URL: pusher.workadventure.localhost
|
PUSHER_URL: //pusher.workadventure.localhost
|
||||||
UPLOADER_URL: uploader.workadventure.localhost
|
UPLOADER_URL: //uploader.workadventure.localhost
|
||||||
ADMIN_URL: admin.workadventure.localhost
|
ADMIN_URL: //workadventure.localhost
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: ./templater.sh
|
||||||
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
|
STARTUP_COMMAND_2: yarn install
|
||||||
TURN_USER: workadventure
|
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||||
TURN_PASSWORD: WorkAdventure123
|
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
||||||
|
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||||
|
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||||
|
TURN_USER: ""
|
||||||
|
TURN_PASSWORD: ""
|
||||||
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
command: yarn run start
|
command: yarn run start
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/usr/src/app
|
- ./front:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
||||||
- "traefik.http.routers.front.entryPoints=web,traefik"
|
- "traefik.http.routers.front.entryPoints=web"
|
||||||
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||||
- "traefik.http.routers.front-ssl.rule=Host(`play.workadventure.localhost`)"
|
- "traefik.http.routers.front-ssl.rule=Host(`play.workadventure.localhost`)"
|
||||||
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||||
@ -48,10 +53,8 @@ services:
|
|||||||
pusher:
|
pusher:
|
||||||
image: thecodingmachine/nodejs:12
|
image: thecodingmachine/nodejs:12
|
||||||
command: yarn dev
|
command: yarn dev
|
||||||
#command: yarn run prod
|
|
||||||
#command: yarn run profile
|
|
||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "socket:*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
@ -106,6 +109,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:
|
||||||
@ -135,23 +139,6 @@ services:
|
|||||||
- "traefik.http.routers.uploader-ssl.tls=true"
|
- "traefik.http.routers.uploader-ssl.tls=true"
|
||||||
- "traefik.http.routers.uploader-ssl.service=uploader"
|
- "traefik.http.routers.uploader-ssl.service=uploader"
|
||||||
|
|
||||||
website:
|
|
||||||
image: thecodingmachine/nodejs:12-apache
|
|
||||||
environment:
|
|
||||||
STARTUP_COMMAND_1: npm install
|
|
||||||
STARTUP_COMMAND_2: npm run watch &
|
|
||||||
APACHE_DOCUMENT_ROOT: dist/
|
|
||||||
volumes:
|
|
||||||
- ./website:/var/www/html
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
|
|
||||||
- "traefik.http.routers.website.entryPoints=web"
|
|
||||||
- "traefik.http.services.website.loadbalancer.server.port=80"
|
|
||||||
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
|
|
||||||
- "traefik.http.routers.website-ssl.entryPoints=websecure"
|
|
||||||
- "traefik.http.routers.website-ssl.tls=true"
|
|
||||||
- "traefik.http.routers.website-ssl.service=website"
|
|
||||||
|
|
||||||
messages:
|
messages:
|
||||||
#image: thecodingmachine/nodejs:14
|
#image: thecodingmachine/nodejs:14
|
||||||
image: thecodingmachine/workadventure-back-base:latest
|
image: thecodingmachine/workadventure-back-base:latest
|
||||||
@ -164,3 +151,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/.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
/dist/webpack.config.js.map
|
/dist/webpack.config.js.map
|
||||||
/dist/src
|
/dist/src
|
||||||
*.sh
|
*.sh
|
||||||
|
!templater.sh
|
||||||
|
@ -3,13 +3,19 @@ WORKDIR /var/www/messages
|
|||||||
COPY --chown=docker:docker messages .
|
COPY --chown=docker:docker messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
# we are rebuilding on each deploy to cope with the API_URL environment URL
|
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
|
||||||
FROM thecodingmachine/nodejs:14-apache
|
FROM thecodingmachine/nodejs:14-apache
|
||||||
|
|
||||||
COPY --chown=docker:docker front .
|
COPY --chown=docker:docker front .
|
||||||
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
|
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
|
||||||
|
|
||||||
|
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
||||||
|
# iframe.html is only in dev mode to circumvent a limitation
|
||||||
|
RUN rm dist/iframe.html
|
||||||
|
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
ENV STARTUP_COMMAND_0="./templater.sh"
|
||||||
ENV STARTUP_COMMAND_1="yarn run build"
|
ENV STARTUP_COMMAND_1="yarn run build"
|
||||||
ENV APACHE_DOCUMENT_ROOT=dist/
|
ENV APACHE_DOCUMENT_ROOT=dist/
|
||||||
|
4
front/dist/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
index.html
|
||||||
|
index.tmpl.html.tmp
|
||||||
|
/js/
|
||||||
|
style.*.css
|
9
front/dist/ga.html.tmpl
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=<!-- TRACKING NUMBER -->"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', '<!-- TRACKING NUMBER -->');
|
||||||
|
</script>
|
17
front/dist/iframe.html
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script src="/iframe_api.js" ></script>
|
||||||
|
<script>
|
||||||
|
// Note: this is a huge XSS flow as we allow anyone to load a Javascript file in our domain.
|
||||||
|
// This file must ABSOLUTELY be removed from the Docker images/deployments and is only here
|
||||||
|
// for development purpose (because dynamically generated iframes are not working with
|
||||||
|
// webpack hot reload due to an issue with rights)
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const scriptUrl = urlParams.get('script');
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = scriptUrl;
|
||||||
|
document.head.append(script);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
@ -6,15 +6,8 @@
|
|||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
|
||||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
<!-- TRACK CODE -->
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
|
<!-- END TRACK CODE -->
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
|
|
||||||
gtag('config', 'UA-10196481-11');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
|
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
|
||||||
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
|
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
|
||||||
@ -36,7 +29,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<link rel="stylesheet" href="/resources/style/style.css">
|
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||||
|
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
<title>WorkAdventure</title>
|
<title>WorkAdventure</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="body" style="margin: 0; background-color: #000">
|
<body id="body" style="margin: 0; background-color: #000">
|
||||||
@ -73,61 +68,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="cowebsite" class="cowebsite hidden">
|
<div id="cowebsite" class="cowebsite hidden">
|
||||||
<button class="close-btn" id="cowebsite-close">
|
<aside id="cowebsite-aside">
|
||||||
|
<img src="/static/images/menu.svg" alt="hold to resize"/>
|
||||||
|
</aside>
|
||||||
|
<main id="cowebsite-main">
|
||||||
|
</main>
|
||||||
|
<button class="top-right-btn" id="cowebsite-fullscreen" alt="fullscreen mode">
|
||||||
|
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
||||||
|
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
||||||
|
</button>
|
||||||
|
<button class="top-right-btn" id="cowebsite-close" alt="close the iframe">
|
||||||
<img src="resources/logos/close.svg"/>
|
<img src="resources/logos/close.svg"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="audio-playing">
|
|
||||||
<img src="/resources/logos/megaphone.svg"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="audioplayerctrl" class="hidden">
|
<div id="audioplayerctrl" class="hidden">
|
||||||
<div class="audioplayer">
|
<div class="audioplayer">
|
||||||
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
|
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
|
||||||
<svg
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-volume-up" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
width="1em"
|
<path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z" />
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
class="bi bi-volume-up"
|
|
||||||
fill="white"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
|
|
||||||
/>
|
|
||||||
<g id="audioplayer_volume_icon_playing">
|
<g id="audioplayer_volume_icon_playing">
|
||||||
<path
|
<path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z" />
|
||||||
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
|
<path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z" />
|
||||||
/>
|
<path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z" />
|
||||||
<path
|
|
||||||
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
|
|
||||||
/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="audioplayer">
|
<div class="audioplayer">
|
||||||
<input
|
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.025" value="1" />
|
||||||
type="range"
|
|
||||||
id="audioplayer_volume"
|
|
||||||
min="0"
|
|
||||||
max="1"
|
|
||||||
step="0.05"
|
|
||||||
value="1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="audioplayer">
|
<div class="audioplayer">
|
||||||
<label
|
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
|
||||||
id="label-audioplayer_decrease_while_talking"
|
reduce in conversations
|
||||||
for="audiooplayer_decrease_while_talking"
|
|
||||||
title="decrease background volume by 50% when entering conversations"
|
|
||||||
>
|
|
||||||
autoreduce
|
|
||||||
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
||||||
</label>
|
</label>
|
||||||
<div id="audioplayer" style="visibility: hidden"></div>
|
<div id="audioplayer" style="visibility: hidden"></div>
|
||||||
@ -137,29 +110,7 @@
|
|||||||
<img src="/resources/logos/megaphone.svg" />
|
<img src="/resources/logos/megaphone.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
|
||||||
<div id="webRtc" class="webrtc">
|
|
||||||
<div id="activeCam" class="activeCam">
|
|
||||||
<div id="div-myCamVideo" class="video-container">
|
|
||||||
<video id="myCamVideo" autoplay muted></video>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="btn-cam-action">
|
|
||||||
<div class="btn-micro">
|
|
||||||
<img id="microphone" src="resources/logos/microphone.svg">
|
|
||||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
|
||||||
</div>
|
|
||||||
<div class="btn-video">
|
|
||||||
<img id="cinema" src="resources/logos/cinema.svg">
|
|
||||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
|
||||||
</div>
|
|
||||||
<div class="btn-monitor">
|
|
||||||
<img id="monitor" src="resources/logos/monitor.svg">
|
|
||||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||||
</div>
|
</div>
|
||||||
<div id="webRtcSetup" class="webrtcsetup">
|
<div id="webRtcSetup" class="webrtcsetup">
|
25
front/dist/resources/html/gameMenu.html
vendored
@ -1,11 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
cursor: url('/resources/logos/cursor_normal.png'), auto;
|
|
||||||
}
|
|
||||||
* a, button, select{
|
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
|
||||||
}
|
|
||||||
#gameMenu button {
|
#gameMenu button {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
color: white;
|
color: white;
|
||||||
@ -15,6 +8,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>
|
||||||
@ -29,14 +30,24 @@
|
|||||||
<section>
|
<section>
|
||||||
<button id="changeSkinButton">Edit skin</button>
|
<button id="changeSkinButton">Edit skin</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<button id="changeCompanionButton">Edit companion</button>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button id="editGameSettingsButton">Settings</button>
|
<button id="editGameSettingsButton">Settings</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<button id="toggleFullscreen">Toggle fullscreen</button>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button id="sparkButton">Create map</button>
|
<button id="sparkButton">Create map</button>
|
||||||
</section>
|
</section>
|
||||||
<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>
|
||||||
|
7
front/dist/resources/html/gameMenuIcon.html
vendored
@ -1,11 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
cursor: url('/resources/logos/cursor_normal.png'), auto;
|
|
||||||
}
|
|
||||||
* a, button, select{
|
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
|
||||||
}
|
|
||||||
#menuIcon button {
|
#menuIcon button {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
cursor: url('/resources/logos/cursor_normal.png'), auto;
|
|
||||||
}
|
|
||||||
* a, button, select{
|
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
|
||||||
}
|
|
||||||
#gameQuality {
|
#gameQuality {
|
||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
border: 1px solid #42464b;
|
border: 1px solid #42464b;
|
||||||
|
115
front/dist/resources/html/gameReport.html
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<style>
|
||||||
|
#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>
|
||||||
|
|
10
front/dist/resources/html/gameShare.html
vendored
@ -1,11 +1,4 @@
|
|||||||
<style>
|
<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;
|
|
||||||
}
|
|
||||||
#gameShare {
|
#gameShare {
|
||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
border: 1px solid #42464b;
|
border: 1px solid #42464b;
|
||||||
@ -14,9 +7,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;
|
||||||
|
103
front/dist/resources/html/helpCameraSettings.html
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<style>
|
||||||
|
#helpCameraSettings {
|
||||||
|
background: #eceeee;
|
||||||
|
border: 1px solid #42464b;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 10px auto 0;
|
||||||
|
width: 400px;
|
||||||
|
height: 370px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings 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;
|
||||||
|
}
|
||||||
|
#helpCameraSettings input {
|
||||||
|
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: 30px;
|
||||||
|
transition: box-shadow 0.3s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section.action{
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#helpCameraSettings button {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
||||||
|
background-color: #c7c7c700;
|
||||||
|
color: #292929;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section a{
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0 6px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section h6,
|
||||||
|
#helpCameraSettings section h5{
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section.text-center{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section p{
|
||||||
|
font-size: 8px;
|
||||||
|
margin: 0px 20px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section p.err{
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section ul{
|
||||||
|
margin: 6px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section li{
|
||||||
|
text-align: left;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
#helpCameraSettings section img {
|
||||||
|
width: 200px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<form id="helpCameraSettings" hidden>
|
||||||
|
<section class="text-center">
|
||||||
|
<h5>Camera/Microphone access needed</h5>
|
||||||
|
<p class="err" id="permissionError">Permission denied</p>
|
||||||
|
<p class="info">You must allow camera and microphone access in your browser.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
||||||
|
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
||||||
|
</ul>
|
||||||
|
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
||||||
|
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||||
|
<p id='browserHelpSetting'></p>
|
||||||
|
</section>
|
||||||
|
<section class="action">
|
||||||
|
<button type="submit" id="helpCameraSettingsFormRefresh">Refresh</button>
|
||||||
|
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
18
front/dist/resources/html/warningContainer.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<style>
|
||||||
|
#warningMain {
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 100px;
|
||||||
|
width: 300px;
|
||||||
|
background-color: red;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#warningMain h2 {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<main id="warningMain">
|
||||||
|
<h2>Warning!</h2>
|
||||||
|
<p>This world is close to its limit!</p>
|
||||||
|
</main>
|
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 |
3
front/dist/resources/logos/fullscreen-exit.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="i-fullscreen-exit" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path d="M4 12 L12 12 12 4 M20 4 L20 12 28 12 M4 20 L12 20 12 28 M28 20 L20 20 20 28" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 329 B |
3
front/dist/resources/logos/fullscreen.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="i-fullscreen" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path d="M4 12 L4 4 12 4 M20 4 L28 4 28 12 M4 20 L4 28 12 28 M28 20 L28 28 20 28" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 322 B |
Before Width: | Height: | Size: 16 KiB 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/help-setting-camera-permission-chrome.png
vendored
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
front/dist/resources/objects/help-setting-camera-permission-firefox.png
vendored
Normal file
After Width: | Height: | Size: 32 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 |
137
front/dist/resources/style/cowebsite.scss
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/* A potentially shared website could appear in an iframe in the cowebsite space. */
|
||||||
|
|
||||||
|
#cowebsite {
|
||||||
|
position: fixed;
|
||||||
|
transition: transform 0.5s;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
background: gray;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin: 3px;
|
||||||
|
pointer-events: none;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right-btn{
|
||||||
|
position: absolute;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 25px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img:hover {
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-aspect-ratio: 1/1) {
|
||||||
|
#cowebsite {
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 50%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
transform: translateX(90%);
|
||||||
|
}
|
||||||
|
&.hidden {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
aside {
|
||||||
|
width: 30px;
|
||||||
|
cursor: ew-resize;
|
||||||
|
|
||||||
|
img {
|
||||||
|
cursor: ew-resize;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right-btn{
|
||||||
|
left: -6px;
|
||||||
|
&#cowebsite-close {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
&#cowebsite-fullscreen {
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-aspect-ratio: 1/1) {
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#cowebsite {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
transform: translateY(-90%);
|
||||||
|
}
|
||||||
|
&.hidden {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
aside {
|
||||||
|
height: 30px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
img {
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right-btn {
|
||||||
|
&#cowebsite-close {
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
&#cowebsite-fullscreen {
|
||||||
|
right: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
front/dist/resources/style/index.scss
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@import "cowebsite.scss";
|
||||||
|
@import "style.css";
|
195
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{
|
||||||
@ -104,6 +151,7 @@ video#myCamVideo{
|
|||||||
|
|
||||||
|
|
||||||
.btn-cam-action {
|
.btn-cam-action {
|
||||||
|
pointer-events: all;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
@ -139,21 +187,26 @@ video#myCamVideo{
|
|||||||
transition: 280ms;
|
transition: 280ms;
|
||||||
}
|
}
|
||||||
.btn-micro{
|
.btn-micro{
|
||||||
|
pointer-events: auto;
|
||||||
transition: all .3s;
|
transition: all .3s;
|
||||||
right: 44px;
|
right: 44px;
|
||||||
}
|
}
|
||||||
.btn-video{
|
.btn-video{
|
||||||
|
pointer-events: auto;
|
||||||
transition: all .25s;
|
transition: all .25s;
|
||||||
right: 134px;
|
right: 134px;
|
||||||
}
|
}
|
||||||
.btn-monitor{
|
.btn-monitor{
|
||||||
|
pointer-events: auto;
|
||||||
transition: all .2s;
|
transition: all .2s;
|
||||||
right: 224px;
|
right: 224px;
|
||||||
}
|
}
|
||||||
/*.btn-call{
|
.btn-copy{
|
||||||
transition: all .1s;
|
pointer-events: auto;
|
||||||
left: 0px;
|
transition: all .3s;
|
||||||
}*/
|
right: 44px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
.btn-cam-action div img{
|
.btn-cam-action div img{
|
||||||
height: 22px;
|
height: 22px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
@ -281,35 +334,7 @@ body {
|
|||||||
max-height: 25%;
|
max-height: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cowebsite {
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 50%;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
#cowebsite.loading {
|
|
||||||
transform: translateX(90%);
|
|
||||||
}
|
|
||||||
#cowebsite.hidden {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite .close-btn{
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: -100px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
|
||||||
animation: right .2s ease;
|
|
||||||
}
|
|
||||||
#cowebsite .close-btn img{
|
|
||||||
height: 15px;
|
|
||||||
right: 15px;
|
|
||||||
}
|
|
||||||
#cowebsite:hover .close-btn{
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@media (max-aspect-ratio: 1/1) {
|
@media (max-aspect-ratio: 1/1) {
|
||||||
.game-overlay {
|
.game-overlay {
|
||||||
@ -328,19 +353,6 @@ body {
|
|||||||
.sidebar > div:hover {
|
.sidebar > div:hover {
|
||||||
max-width: 25%;
|
max-width: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cowebsite {
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 50%;
|
|
||||||
}
|
|
||||||
#cowebsite.loading {
|
|
||||||
transform: translateY(90%);
|
|
||||||
}
|
|
||||||
#cowebsite.hidden {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#game {
|
#game {
|
||||||
@ -348,20 +360,6 @@ body {
|
|||||||
position: relative; /* Position relative is needed for the game-overlay. */
|
position: relative; /* Position relative is needed for the game-overlay. */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A potentially shared website could appear in an iframe in the cowebsite space. */
|
|
||||||
#cowebsite {
|
|
||||||
position: fixed;
|
|
||||||
transition: transform 0.5s;
|
|
||||||
}
|
|
||||||
#cowebsite.loading {
|
|
||||||
background-color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite > iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioplayer:first-child {
|
.audioplayer:first-child {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid: 2rem / 4rem 10rem;
|
grid: 2rem / 4rem 10rem;
|
||||||
@ -374,10 +372,14 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audioplayer > div {
|
||||||
|
padding-right: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
#audioplayerctrl {
|
#audioplayerctrl {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 50%;
|
right: calc(50% - 120px);
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
color: white;
|
color: white;
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
@ -509,6 +511,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
/* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */
|
/* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,6 +547,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
flex: 0 0 25%;
|
flex: 0 0 25%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar > div {
|
.sidebar > div {
|
||||||
@ -567,10 +571,9 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-mode {
|
.chat-mode {
|
||||||
display: flex;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
padding: 1%;
|
padding: 1%;
|
||||||
@ -586,24 +589,20 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
.chat-mode > div:hover {
|
.chat-mode > div:hover {
|
||||||
margin: 0%;
|
margin: 0%;
|
||||||
}
|
}
|
||||||
.chat-mode.one-col > div {
|
.chat-mode.one-col {
|
||||||
flex-basis: 98%;
|
grid-template-columns: repeat(1, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-mode.two-col > div {
|
.chat-mode.two-col {
|
||||||
flex-basis: 48%;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-mode.three-col > div {
|
.chat-mode.three-col {
|
||||||
flex-basis: 31.333333%;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-mode.four-col > div {
|
.chat-mode.four-col {
|
||||||
flex-basis: 23%;
|
grid-template-columns: repeat(4, 1fr);
|
||||||
}
|
|
||||||
|
|
||||||
.chat-mode > div:last-child {
|
|
||||||
flex-grow: 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*CONSOLE*/
|
/*CONSOLE*/
|
||||||
@ -1072,17 +1071,22 @@ div.modal-report-user{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.discussion .messages .message p.body{
|
.discussion .messages .message p.body{
|
||||||
|
color: white;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
.discussion .messages .message p a{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.discussion .send-message{
|
.discussion .send-message{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 45px;
|
bottom: 45px;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion .send-message input{
|
.discussion .send-message input{
|
||||||
@ -1130,6 +1134,31 @@ div.action p.action-body{
|
|||||||
margin-left: calc(50% - 75px);
|
margin-left: calc(50% - 75px);
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
.popUpElement{
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
text-align: left;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.popUpElement div {
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
font-size: 10px;
|
||||||
|
background-color: #727678;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popUpElement button {
|
||||||
|
position: relative;
|
||||||
|
font-size: 10px;
|
||||||
|
border-image-repeat: revert;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popUpElement .buttonContainer {
|
||||||
|
float: right;
|
||||||
|
background-color: inherit;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes mymove {
|
@keyframes mymove {
|
||||||
0% {bottom: 40px;}
|
0% {bottom: 40px;}
|
||||||
50% {bottom: 30px;}
|
50% {bottom: 30px;}
|
||||||
|
@ -9,9 +9,13 @@
|
|||||||
"@types/quill": "^1.3.7",
|
"@types/quill": "^1.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^2.26.0",
|
||||||
|
"css-loader": "^5.1.3",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"html-webpack-plugin": "^4.3.0",
|
||||||
"jasmine": "^3.5.0",
|
"jasmine": "^3.5.0",
|
||||||
|
"mini-css-extract-plugin": "^1.3.9",
|
||||||
|
"sass": "^1.32.8",
|
||||||
|
"sass-loader": "10.1.1",
|
||||||
"ts-loader": "^6.2.2",
|
"ts-loader": "^6.2.2",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
@ -26,9 +30,11 @@
|
|||||||
"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.22.0",
|
"phaser": "3.24.1",
|
||||||
|
"phaser3-rex-plugins": "^1.1.42",
|
||||||
"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"
|
||||||
|
1
front/packages/iframe-api-typings/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
iframe_api.d.ts
|
0
front/packages/iframe-api-typings/.npmignore
Normal file
27
front/packages/iframe-api-typings/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<h1 align="center">WorkAdventure - IFrame API typings for Typescript</h1>
|
||||||
|
|
||||||
|
<p align="center">This package contains Typescript typings for <a href="https://workadventu.re/map-building/scripting">WorkAdventure's map scripting API</a></p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
[WorkAdventure](https://workadventu.re) comes with a scripting API. Using this API, you can add some intelligence to your map.
|
||||||
|
You use this API by loading an external script directly from WorkAdventure (at https://play.workadventu.re/iframe_api.js), or this script is loaded
|
||||||
|
for you if you are using the "script" property of a map.
|
||||||
|
|
||||||
|
This project contains Typescript typings for the `WA` object provided by this script.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This package is only useful if you are using Typescript to script your WorkAdventure maps.
|
||||||
|
|
||||||
|
## Download & Installation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ npm install @workadventure/iframe-api-typings
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ yarn add @workadventure/iframe-api-typings
|
||||||
|
```
|
1
front/packages/iframe-api-typings/iframe_api.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
// This file is voluntarily empty.
|
13
front/packages/iframe-api-typings/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "@workadventure/iframe-api-typings",
|
||||||
|
"version": "VERSION_PLACEHOLDER",
|
||||||
|
"description": "Typescript typings for WorkAdventure iFrame API",
|
||||||
|
"main": "iframe_api.js",
|
||||||
|
"types": "iframe_api.d.ts",
|
||||||
|
"repository": "https://github.com/thecodingmachine/workadventure/",
|
||||||
|
"author": "David Négrier <d.negrier@thecodingmachine.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
|||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
|
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||||
|
|
||||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||||
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
||||||
@ -10,13 +11,16 @@ export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
|
|||||||
export const INPUT_TYPE_CONSOLE = 'input-type';
|
export const INPUT_TYPE_CONSOLE = 'input-type';
|
||||||
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
|
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
|
||||||
|
|
||||||
export const AUDIO_TYPE = 'audio';
|
export const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||||
export const MESSAGE_TYPE = 'message';
|
export const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||||
|
|
||||||
interface EventTargetFiles extends EventTarget {
|
interface EventTargetFiles extends EventTarget {
|
||||||
files: Array<File>;
|
files: Array<File>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
export class ConsoleGlobalMessageManager {
|
export class ConsoleGlobalMessageManager {
|
||||||
|
|
||||||
private readonly divMainConsole: HTMLDivElement;
|
private readonly divMainConsole: HTMLDivElement;
|
||||||
@ -140,7 +144,7 @@ export class ConsoleGlobalMessageManager {
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.id = INPUT_CONSOLE_MESSAGE
|
div.id = INPUT_CONSOLE_MESSAGE
|
||||||
const buttonSend = document.createElement('button');
|
const buttonSend = document.createElement('button');
|
||||||
buttonSend.innerText = 'Envoyer';
|
buttonSend.innerText = 'Send';
|
||||||
buttonSend.classList.add('btn');
|
buttonSend.classList.add('btn');
|
||||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||||
this.sendMessage();
|
this.sendMessage();
|
||||||
@ -242,7 +246,7 @@ export class ConsoleGlobalMessageManager {
|
|||||||
div.appendChild(input);
|
div.appendChild(input);
|
||||||
|
|
||||||
const buttonSend = document.createElement('button');
|
const buttonSend = document.createElement('button');
|
||||||
buttonSend.innerText = 'Envoyer';
|
buttonSend.innerText = 'Send';
|
||||||
buttonSend.classList.add('btn');
|
buttonSend.classList.add('btn');
|
||||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||||
this.sendMessage();
|
this.sendMessage();
|
||||||
@ -332,7 +336,7 @@ export class ConsoleGlobalMessageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
active(){
|
active(){
|
||||||
this.userInputManager.clearAllInputKeyboard();
|
this.userInputManager.disableControls();
|
||||||
this.divMainConsole.style.top = '0';
|
this.divMainConsole.style.top = '0';
|
||||||
this.activeConsole = true;
|
this.activeConsole = true;
|
||||||
}
|
}
|
||||||
@ -372,23 +376,6 @@ export class ConsoleGlobalMessageManager {
|
|||||||
this.buttonSendMainConsole.classList.remove('active');
|
this.buttonSendMainConsole.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*activeSettingConsole(){
|
|
||||||
this.activeSetting = true;
|
|
||||||
if(this.activeMessage){
|
|
||||||
this.disabledSettingConsole();
|
|
||||||
}
|
|
||||||
this.active();
|
|
||||||
this.divSettingConsole.classList.add('active');
|
|
||||||
//this.buttonSettingsMainConsole.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
disabledSettingConsole(){
|
|
||||||
this.activeSetting = false;
|
|
||||||
this.disabled();
|
|
||||||
this.divSettingConsole.classList.remove('active');
|
|
||||||
//this.buttonSettingsMainConsole.classList.remove('active');
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private getSectionId(id: string) : string {
|
private getSectionId(id: string) : string {
|
||||||
return `section-${id}`;
|
return `section-${id}`;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||||
import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
|
@ -77,8 +77,10 @@ export class TypeMessageExt implements TypeMessageInterface{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class Ban extends TypeMessageExt {
|
|
||||||
}
|
export class Message extends TypeMessageExt {}
|
||||||
|
|
||||||
|
export class Ban extends TypeMessageExt {}
|
||||||
|
|
||||||
export class Banned extends TypeMessageExt {
|
export class Banned extends TypeMessageExt {
|
||||||
showMessage(message: string){
|
showMessage(message: string){
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
|
||||||
import * as TypeMessages from "./TypeMessage";
|
import * as TypeMessages from "./TypeMessage";
|
||||||
|
import {Banned} from "./TypeMessage";
|
||||||
|
import {adminMessagesService} from "../Connexion/AdminMessagesService";
|
||||||
|
|
||||||
export interface TypeMessageInterface {
|
export interface TypeMessageInterface {
|
||||||
showMessage(message: string): void;
|
showMessage(message: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserMessageManager {
|
class UserMessageManager {
|
||||||
|
|
||||||
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
||||||
|
receiveBannedMessageListener!: Function;
|
||||||
|
|
||||||
constructor(private Connection: RoomConnection) {
|
constructor() {
|
||||||
const valueTypeMessageTab = Object.values(TypeMessages);
|
const valueTypeMessageTab = Object.values(TypeMessages);
|
||||||
Object.keys(TypeMessages).forEach((value: string, index: number) => {
|
Object.keys(TypeMessages).forEach((value: string, index: number) => {
|
||||||
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
|
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
|
||||||
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
|
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
|
||||||
});
|
});
|
||||||
this.initialise();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialise() {
|
adminMessagesService.messageStream.subscribe((event) => {
|
||||||
//receive signal to show message
|
const typeMessage = this.showMessage(event.type, event.text);
|
||||||
this.Connection.receiveUserMessage((type: string, message: string) => {
|
if(typeMessage instanceof Banned) {
|
||||||
this.showMessage(type, message);
|
this.receiveBannedMessageListener();
|
||||||
});
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage(type: string, message: string) {
|
showMessage(type: string, message: string) {
|
||||||
@ -32,5 +33,11 @@ export class UserMessageManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
classTypeMessage.showMessage(message);
|
classTypeMessage.showMessage(message);
|
||||||
|
return classTypeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReceiveBanListener(callback: Function){
|
||||||
|
this.receiveBannedMessageListener = callback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const userMessageManager = new UserMessageManager()
|
11
front/src/Api/Events/ButtonClickedEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isButtonClickedEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
popupId: tg.isNumber,
|
||||||
|
buttonId: tg.isNumber,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||||
|
*/
|
||||||
|
export type ButtonClickedEvent = tg.GuardedType<typeof isButtonClickedEvent>;
|
11
front/src/Api/Events/ChatEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isChatEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
message: tg.isString,
|
||||||
|
author: tg.isString,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type ChatEvent = tg.GuardedType<typeof isChatEvent>;
|
11
front/src/Api/Events/ClosePopupEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isClosePopupEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
popupId: tg.isNumber,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type ClosePopupEvent = tg.GuardedType<typeof isClosePopupEvent>;
|
10
front/src/Api/Events/EnterLeaveEvent.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isEnterLeaveEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||||
|
*/
|
||||||
|
export type EnterLeaveEvent = tg.GuardedType<typeof isEnterLeaveEvent>;
|
13
front/src/Api/Events/GoToPageEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const isGoToPageEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type GoToPageEvent = tg.GuardedType<typeof isGoToPageEvent>;
|
7
front/src/Api/Events/IframeEvent.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface IframeEvent {
|
||||||
|
type: string;
|
||||||
|
data: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string';
|
13
front/src/Api/Events/OpenCoWebSiteEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const isOpenCoWebsite =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type OpenCoWebSiteEvent = tg.GuardedType<typeof isOpenCoWebsite>;
|
20
front/src/Api/Events/OpenPopupEvent.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
const isButtonDescriptor =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
label: tg.isString,
|
||||||
|
className: tg.isOptional(tg.isString)
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
export const isOpenPopupEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
popupId: tg.isNumber,
|
||||||
|
targetObject: tg.isString,
|
||||||
|
message: tg.isString,
|
||||||
|
buttons: tg.isArray(isButtonDescriptor)
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type OpenPopupEvent = tg.GuardedType<typeof isOpenPopupEvent>;
|
13
front/src/Api/Events/OpenTabEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const isOpenTabEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type OpenTabEvent = tg.GuardedType<typeof isOpenTabEvent>;
|
10
front/src/Api/Events/UserInputChatEvent.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isUserInputChatEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
message: tg.isString,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the game to the iFrame when a user types a message in the chat.
|
||||||
|
*/
|
||||||
|
export type UserInputChatEvent = tg.GuardedType<typeof isUserInputChatEvent>;
|
238
front/src/Api/IframeListener.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
||||||
|
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
|
||||||
|
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
|
import {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
||||||
|
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
||||||
|
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
||||||
|
import {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
||||||
|
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
||||||
|
import {scriptUtils} from "./ScriptUtils";
|
||||||
|
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
||||||
|
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
|
* Also allows to send messages to those iframes.
|
||||||
|
*/
|
||||||
|
class IframeListener {
|
||||||
|
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||||
|
public readonly chatStream = this._chatStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _openPopupStream: Subject<OpenPopupEvent> = new Subject();
|
||||||
|
public readonly openPopupStream = this._openPopupStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _openTabStream: Subject<OpenTabEvent> = new Subject();
|
||||||
|
public readonly openTabStream = this._openTabStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _goToPageStream: Subject<GoToPageEvent> = new Subject();
|
||||||
|
public readonly goToPageStream = this._goToPageStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _openCoWebSiteStream: Subject<OpenCoWebSiteEvent> = new Subject();
|
||||||
|
public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _closeCoWebSiteStream: Subject<void> = new Subject();
|
||||||
|
public readonly closeCoWebSiteStream = this._closeCoWebSiteStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
||||||
|
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
||||||
|
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _closePopupStream: Subject<ClosePopupEvent> = new Subject();
|
||||||
|
public readonly closePopupStream = this._closePopupStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _displayBubbleStream: Subject<void> = new Subject();
|
||||||
|
public readonly displayBubbleStream = this._displayBubbleStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _removeBubbleStream: Subject<void> = new Subject();
|
||||||
|
public readonly removeBubbleStream = this._removeBubbleStream.asObservable();
|
||||||
|
|
||||||
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
|
|
||||||
|
init() {
|
||||||
|
window.addEventListener("message", (message) => {
|
||||||
|
// Do we trust the sender of this message?
|
||||||
|
// Let's only accept messages from the iframe that are allowed.
|
||||||
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
|
let found = false;
|
||||||
|
for (const iframe of this.iframes) {
|
||||||
|
if (iframe.contentWindow === message.source) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = message.data;
|
||||||
|
if (isIframeEventWrapper(payload)) {
|
||||||
|
if (payload.type === 'chat' && isChatEvent(payload.data)) {
|
||||||
|
this._chatStream.next(payload.data);
|
||||||
|
} else if (payload.type === 'openPopup' && isOpenPopupEvent(payload.data)) {
|
||||||
|
this._openPopupStream.next(payload.data);
|
||||||
|
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
||||||
|
this._closePopupStream.next(payload.data);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
||||||
|
scriptUtils.openTab(payload.data.url);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||||
|
scriptUtils.goToPage(payload.data.url);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||||
|
scriptUtils.openCoWebsite(payload.data.url);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'closeCoWebSite') {
|
||||||
|
scriptUtils.closeCoWebSite();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'disablePlayerControl'){
|
||||||
|
this._disablePlayerControlStream.next();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'restorePlayerControl'){
|
||||||
|
this._enablePlayerControlStream.next();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'displayBubble'){
|
||||||
|
this._displayBubbleStream.next();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'removeBubble'){
|
||||||
|
this._removeBubbleStream.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the passed iFrame to send/receive messages via the API.
|
||||||
|
*/
|
||||||
|
registerIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
this.iframes.add(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
this.iframes.delete(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerScript(scriptUrl: string): void {
|
||||||
|
console.log('Loading map related script at ', scriptUrl)
|
||||||
|
|
||||||
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||||
|
// Using external iframe mode (
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.id = this.getIFrameId(scriptUrl);
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe.src = '/iframe.html?script='+encodeURIComponent(scriptUrl);
|
||||||
|
|
||||||
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
|
iframe.sandbox.add('allow-scripts');
|
||||||
|
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||||
|
|
||||||
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
|
this.scripts.set(scriptUrl, iframe);
|
||||||
|
this.registerIframe(iframe);
|
||||||
|
} else {
|
||||||
|
// production code
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.id = this.getIFrameId(scriptUrl);
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
|
||||||
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
|
iframe.sandbox.add('allow-scripts');
|
||||||
|
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||||
|
|
||||||
|
const html = '<!doctype html>\n' +
|
||||||
|
'\n' +
|
||||||
|
'<html lang="en">\n' +
|
||||||
|
'<head>\n' +
|
||||||
|
'<script src="'+window.location.protocol+'//'+window.location.host+'/iframe_api.js" ></script>\n' +
|
||||||
|
'<script src="'+scriptUrl+'" ></script>\n' +
|
||||||
|
'</head>\n' +
|
||||||
|
'</html>\n';
|
||||||
|
|
||||||
|
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||||
|
iframe.srcdoc = html;
|
||||||
|
|
||||||
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
|
this.scripts.set(scriptUrl, iframe);
|
||||||
|
this.registerIframe(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIFrameId(scriptUrl: string): string {
|
||||||
|
return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterScript(scriptUrl: string): void {
|
||||||
|
const iFrameId = this.getIFrameId(scriptUrl);
|
||||||
|
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||||
|
if (!iframe) {
|
||||||
|
throw new Error('Unknown iframe for script "'+scriptUrl+'"');
|
||||||
|
}
|
||||||
|
this.unregisterIframe(iframe);
|
||||||
|
iframe.remove();
|
||||||
|
|
||||||
|
this.scripts.delete(scriptUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendUserInputChat(message: string) {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'userInputChat',
|
||||||
|
'data': {
|
||||||
|
'message': message,
|
||||||
|
} as UserInputChatEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEnterEvent(name: string) {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'enterEvent',
|
||||||
|
'data': {
|
||||||
|
"name": name
|
||||||
|
} as EnterLeaveEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLeaveEvent(name: string) {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'leaveEvent',
|
||||||
|
'data': {
|
||||||
|
"name": name
|
||||||
|
} as EnterLeaveEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'buttonClickedEvent',
|
||||||
|
'data': {
|
||||||
|
popupId,
|
||||||
|
buttonId
|
||||||
|
} as ButtonClickedEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the message... to all allowed iframes.
|
||||||
|
*/
|
||||||
|
private postMessage(message: IframeEvent) {
|
||||||
|
for (const iframe of this.iframes) {
|
||||||
|
iframe.contentWindow?.postMessage(message, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const iframeListener = new IframeListener();
|
23
front/src/Api/ScriptUtils.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {coWebsiteManager} from "../WebRtc/CoWebsiteManager";
|
||||||
|
|
||||||
|
class ScriptUtils {
|
||||||
|
|
||||||
|
public openTab(url : string){
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public goToPage(url : string){
|
||||||
|
window.location.href = url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public openCoWebsite(url : string){
|
||||||
|
coWebsiteManager.loadCoWebsite(url,url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeCoWebSite(){
|
||||||
|
coWebsiteManager.closeCoWebsite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const scriptUtils = new ScriptUtils();
|
34
front/src/Connexion/AdminMessagesService.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
|
export enum AdminMessageEventTypes {
|
||||||
|
admin = 'message',
|
||||||
|
audio = 'audio',
|
||||||
|
ban = 'ban',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AdminMessageEvent {
|
||||||
|
type: AdminMessageEventTypes,
|
||||||
|
text: string;
|
||||||
|
//todo add optional properties for other event types
|
||||||
|
}
|
||||||
|
|
||||||
|
//this class is designed to easily allow communication between the RoomConnection objects (that receive the message)
|
||||||
|
//and the various objects that may render the message on screen
|
||||||
|
class AdminMessagesService {
|
||||||
|
private _messageStream: Subject<AdminMessageEvent> = new Subject();
|
||||||
|
public messageStream = this._messageStream.asObservable();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.messageStream.subscribe((event) => console.log('message', event))
|
||||||
|
}
|
||||||
|
|
||||||
|
onSendusermessage(message: SendUserMessage|BanUserMessage) {
|
||||||
|
this._messageStream.next({
|
||||||
|
type: message.getType() as unknown as AdminMessageEventTypes,
|
||||||
|
text: message.getMessage(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const adminMessagesService = new AdminMessagesService();
|
@ -1,5 +1,5 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
|
||||||
import {RoomConnection} from "./RoomConnection";
|
import {RoomConnection} from "./RoomConnection";
|
||||||
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||||
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
||||||
@ -7,12 +7,24 @@ import {localUserStore} from "./LocalUserStore";
|
|||||||
import {LocalUser} from "./LocalUser";
|
import {LocalUser} from "./LocalUser";
|
||||||
import {Room} from "./Room";
|
import {Room} from "./Room";
|
||||||
|
|
||||||
const URL_ROOM_STARTED = '/Floor0/floor0.json';
|
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!:LocalUser;
|
private localUser!:LocalUser;
|
||||||
|
|
||||||
private connexionType?: GameConnexionTypes
|
private connexionType?: GameConnexionTypes
|
||||||
|
private reconnectingTimeout: NodeJS.Timeout|null = null;
|
||||||
|
private _unloading:boolean = false;
|
||||||
|
|
||||||
|
get unloading () {
|
||||||
|
return this._unloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
this._unloading = true;
|
||||||
|
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout)
|
||||||
|
})
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Tries to login to the node server and return the starting map url to be loaded
|
* Tries to login to the node server and return the starting map url to be loaded
|
||||||
*/
|
*/
|
||||||
@ -22,16 +34,16 @@ class ConnectionManager {
|
|||||||
this.connexionType = connexionType;
|
this.connexionType = connexionType;
|
||||||
if(connexionType === GameConnexionTypes.register) {
|
if(connexionType === GameConnexionTypes.register) {
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
const data = await Axios.post(`${PUSHER_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
||||||
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
|
|
||||||
const organizationSlug = data.organizationSlug;
|
const organizationSlug = data.organizationSlug;
|
||||||
const worldSlug = data.worldSlug;
|
const worldSlug = data.worldSlug;
|
||||||
const roomSlug = data.roomSlug;
|
const roomSlug = data.roomSlug;
|
||||||
urlManager.editUrlForRoom(roomSlug, organizationSlug, worldSlug);
|
|
||||||
|
|
||||||
const room = new Room(window.location.pathname + window.location.hash);
|
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.search + window.location.hash);
|
||||||
|
urlManager.pushRoomIdToUrl(room);
|
||||||
return Promise.resolve(room);
|
return Promise.resolve(room);
|
||||||
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
||||||
const localUser = localUserStore.getLocalUser();
|
const localUser = localUserStore.getLocalUser();
|
||||||
@ -50,23 +62,22 @@ class ConnectionManager {
|
|||||||
}
|
}
|
||||||
let roomId: string
|
let roomId: string
|
||||||
if (connexionType === GameConnexionTypes.empty) {
|
if (connexionType === GameConnexionTypes.empty) {
|
||||||
const defaultMapUrl = window.location.host.replace('play.', 'maps.') + URL_ROOM_STARTED;
|
roomId = START_ROOM_URL;
|
||||||
roomId = urlManager.editUrlForRoom(defaultMapUrl, null, null);
|
|
||||||
} else {
|
} else {
|
||||||
roomId = window.location.pathname + window.location.hash;
|
roomId = window.location.pathname + window.location.search + window.location.hash;
|
||||||
}
|
}
|
||||||
return Promise.resolve(new Room(roomId));
|
return Promise.resolve(new Room(roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject('Invalid URL');
|
return Promise.reject(new Error('Invalid URL'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async verifyToken(token: string): Promise<void> {
|
private async verifyToken(token: string): Promise<void> {
|
||||||
await Axios.get(`${API_URL}/verify`, {params: {token}});
|
await Axios.get(`${PUSHER_URL}/verify`, {params: {token}});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||||
const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data);
|
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then(res => res.data);
|
||||||
this.localUser = new LocalUser(data.userUuid, data.authToken, []);
|
this.localUser = new LocalUser(data.userUuid, data.authToken, []);
|
||||||
if (!isBenchmark) { // In benchmark, we don't have a local storage.
|
if (!isBenchmark) { // In benchmark, we don't have a local storage.
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
@ -77,9 +88,9 @@ class ConnectionManager {
|
|||||||
this.localUser = new LocalUser('', 'test', []);
|
this.localUser = new LocalUser('', 'test', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise<OnConnectInterface> {
|
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null): Promise<OnConnectInterface> {
|
||||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||||
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
|
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport, companion);
|
||||||
connection.onConnectError((error: object) => {
|
connection.onConnectError((error: object) => {
|
||||||
console.log('An error occurred while connecting to socket server. Retrying');
|
console.log('An error occurred while connecting to socket server. Retrying');
|
||||||
reject(error);
|
reject(error);
|
||||||
@ -97,10 +108,10 @@ class ConnectionManager {
|
|||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// Let's retry in 4-6 seconds
|
// Let's retry in 4-6 seconds
|
||||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||||
setTimeout(() => {
|
this.reconnectingTimeout = setTimeout(() => {
|
||||||
//todo: allow a way to break recursion?
|
//todo: allow a way to break recursion?
|
||||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport, companion).then((connection) => resolve(connection));
|
||||||
}, 4000 + Math.floor(Math.random() * 2000) );
|
}, 4000 + Math.floor(Math.random() * 2000) );
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {PlayerAnimationNames} from "../Phaser/Player/Animation";
|
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
import {SignalData} from "simple-peer";
|
import {SignalData} from "simple-peer";
|
||||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character";
|
|
||||||
import {RoomConnection} from "./RoomConnection";
|
import {RoomConnection} from "./RoomConnection";
|
||||||
|
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||||
|
|
||||||
export enum EventMessage{
|
export enum EventMessage{
|
||||||
CONNECT = "connect",
|
CONNECT = "connect",
|
||||||
@ -42,19 +42,12 @@ export interface PointInterface {
|
|||||||
moving: boolean;
|
moving: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Point implements PointInterface{
|
|
||||||
constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) {
|
|
||||||
if(x === null || y === null){
|
|
||||||
throw Error("position x and y cannot be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageUserPositionInterface {
|
export interface MessageUserPositionInterface {
|
||||||
userId: number;
|
userId: number;
|
||||||
name: string;
|
name: string;
|
||||||
characterLayers: BodyResourceDescriptionInterface[];
|
characterLayers: BodyResourceDescriptionInterface[];
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
|
companion: string|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageUserMovedInterface {
|
export interface MessageUserMovedInterface {
|
||||||
@ -66,7 +59,8 @@ export interface MessageUserJoined {
|
|||||||
userId: number;
|
userId: number;
|
||||||
name: string;
|
name: string;
|
||||||
characterLayers: BodyResourceDescriptionInterface[];
|
characterLayers: BodyResourceDescriptionInterface[];
|
||||||
position: PointInterface
|
position: PointInterface;
|
||||||
|
companion: string|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
@ -80,23 +74,15 @@ export interface GroupCreatedUpdatedMessageInterface {
|
|||||||
groupSize: number
|
groupSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebRtcStartMessageInterface {
|
|
||||||
roomId: string,
|
|
||||||
clients: UserSimplePeerInterface[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebRtcDisconnectMessageInterface {
|
export interface WebRtcDisconnectMessageInterface {
|
||||||
userId: number
|
userId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebRtcSignalSentMessageInterface {
|
|
||||||
receiverId: number,
|
|
||||||
signal: SignalData
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
@ -111,11 +97,6 @@ export interface ViewportInterface {
|
|||||||
bottom: number,
|
bottom: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BatchedMessageInterface {
|
|
||||||
event: string,
|
|
||||||
payload: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemEventMessageInterface {
|
export interface ItemEventMessageInterface {
|
||||||
itemId: number,
|
itemId: number,
|
||||||
event: string,
|
event: string,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import {MAX_USERNAME_LENGTH} from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export interface CharacterTexture {
|
export interface CharacterTexture {
|
||||||
id: number,
|
id: number,
|
||||||
level: number,
|
level: number,
|
||||||
@ -5,6 +7,23 @@ export interface CharacterTexture {
|
|||||||
rights: string
|
rights: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||||
|
|
||||||
|
export function isUserNameValid(value: string): boolean {
|
||||||
|
const regexp = new RegExp('^[A-Za-z]{1,'+maxUserNameLength+'}$');
|
||||||
|
return regexp.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||||
|
if (!value || !value.length) return false;
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
if (/^\w+$/.exec(value[i]) === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export class LocalUser {
|
export class LocalUser {
|
||||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
|
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import {LocalUser} from "./LocalUser";
|
import {areCharacterLayersValid, isUserNameValid, LocalUser} from "./LocalUser";
|
||||||
|
|
||||||
|
const playerNameKey = 'playerName';
|
||||||
|
const selectedPlayerKey = 'selectedPlayer';
|
||||||
|
const customCursorPositionKey = 'customCursorPosition';
|
||||||
const characterLayersKey = 'characterLayers';
|
const characterLayersKey = 'characterLayers';
|
||||||
|
const companionKey = 'companion';
|
||||||
const gameQualityKey = 'gameQuality';
|
const gameQualityKey = 'gameQuality';
|
||||||
const videoQualityKey = 'videoQuality';
|
const videoQualityKey = 'videoQuality';
|
||||||
|
const audioPlayerVolumeKey = 'audioVolume';
|
||||||
|
const audioPlayerMuteKey = 'audioMute';
|
||||||
|
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||||
|
const fullscreenKey = 'fullscreen';
|
||||||
|
|
||||||
//todo: add localstorage fallback
|
|
||||||
class LocalUserStore {
|
class LocalUserStore {
|
||||||
|
|
||||||
saveUser(localUser: LocalUser) {
|
saveUser(localUser: LocalUser) {
|
||||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
localStorage.setItem('localUser', JSON.stringify(localUser));
|
||||||
}
|
}
|
||||||
@ -16,46 +22,92 @@ class LocalUserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setName(name:string): void {
|
setName(name:string): void {
|
||||||
window.localStorage.setItem('playerName', name);
|
localStorage.setItem(playerNameKey, name);
|
||||||
}
|
}
|
||||||
getName(): string {
|
getName(): string|null {
|
||||||
return window.localStorage.getItem('playerName') ?? '';
|
const value = localStorage.getItem(playerNameKey) || '';
|
||||||
|
return isUserNameValid(value) ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||||
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
|
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
||||||
}
|
}
|
||||||
getPlayerCharacterIndex(): number {
|
getPlayerCharacterIndex(): number {
|
||||||
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
|
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
||||||
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
|
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
|
||||||
}
|
}
|
||||||
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
||||||
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
|
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCharacterLayers(layers: string[]): void {
|
setCharacterLayers(layers: string[]): void {
|
||||||
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||||
}
|
}
|
||||||
getCharacterLayers(): string[]|null {
|
getCharacterLayers(): string[]|null {
|
||||||
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
|
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||||
|
return areCharacterLayersValid(value) ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGameQualityValue(): number {
|
setCompanion(companion: string|null): void {
|
||||||
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
|
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
||||||
}
|
}
|
||||||
|
getCompanion(): string|null {
|
||||||
|
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
||||||
|
|
||||||
|
if (typeof companion !== "string" || companion === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return companion;
|
||||||
|
}
|
||||||
|
wasCompanionSet(): boolean {
|
||||||
|
return localStorage.getItem(companionKey) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
setGameQualityValue(value: number): void {
|
setGameQualityValue(value: number): void {
|
||||||
localStorage.setItem(gameQualityKey, '' + value);
|
localStorage.setItem(gameQualityKey, '' + value);
|
||||||
}
|
}
|
||||||
|
getGameQualityValue(): number {
|
||||||
getVideoQualityValue(): number {
|
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
||||||
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoQualityValue(value: number): void {
|
setVideoQualityValue(value: number): void {
|
||||||
localStorage.setItem(videoQualityKey, '' + value);
|
localStorage.setItem(videoQualityKey, '' + value);
|
||||||
}
|
}
|
||||||
|
getVideoQualityValue(): number {
|
||||||
|
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
||||||
|
}
|
||||||
|
|
||||||
|
setAudioPlayerVolume(value: number): void {
|
||||||
|
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
||||||
|
}
|
||||||
|
getAudioPlayerVolume(): number {
|
||||||
|
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
setAudioPlayerMuted(value: boolean): void {
|
||||||
|
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||||
|
}
|
||||||
|
getAudioPlayerMuted(): boolean {
|
||||||
|
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
setHelpCameraSettingsShown(): void {
|
||||||
|
localStorage.setItem(helpCameraSettingsShown, '1');
|
||||||
|
}
|
||||||
|
getHelpCameraSettingsShown(): boolean {
|
||||||
|
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
setFullscreen(value: boolean): void {
|
||||||
|
localStorage.setItem(fullscreenKey, value.toString());
|
||||||
|
}
|
||||||
|
getFullscreen(): boolean {
|
||||||
|
return localStorage.getItem(fullscreenKey) === 'true';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localUserStore = new LocalUserStore();
|
export const localUserStore = new LocalUserStore();
|
@ -1,29 +1,30 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export class Room {
|
export class Room {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private mapUrl: string|undefined;
|
private mapUrl: string|undefined;
|
||||||
private instance: string|undefined;
|
private instance: string|undefined;
|
||||||
|
private _search: URLSearchParams;
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
if (id.startsWith('/')) {
|
const url = new URL(id, 'https://example.com');
|
||||||
id = id.substr(1);
|
|
||||||
|
this.id = url.pathname;
|
||||||
|
|
||||||
|
if (this.id.startsWith('/')) {
|
||||||
|
this.id = this.id.substr(1);
|
||||||
}
|
}
|
||||||
this.id = id;
|
if (this.id.startsWith('_/')) {
|
||||||
if (id.startsWith('_/')) {
|
|
||||||
this.isPublic = true;
|
this.isPublic = true;
|
||||||
} else if (id.startsWith('@/')) {
|
} else if (this.id.startsWith('@/')) {
|
||||||
this.isPublic = false;
|
this.isPublic = false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid room ID');
|
throw new Error('Invalid room ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexOfHash = this.id.indexOf('#');
|
this._search = new URLSearchParams(url.search);
|
||||||
if (indexOfHash !== -1) {
|
|
||||||
this.id = this.id.substr(0, indexOfHash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
|
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
|
||||||
@ -66,14 +67,15 @@ export class Room {
|
|||||||
// We have a private ID, we need to query the map URL from the server.
|
// We have a private ID, we need to query the map URL from the server.
|
||||||
const urlParts = this.parsePrivateUrl(this.id);
|
const urlParts = this.parsePrivateUrl(this.id);
|
||||||
|
|
||||||
Axios.get(`${API_URL}/map`, {
|
Axios.get(`${PUSHER_URL}/map`, {
|
||||||
params: urlParts
|
params: urlParts
|
||||||
}).then(({data}) => {
|
}).then(({data}) => {
|
||||||
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
||||||
resolve(data.mapUrl);
|
resolve(data.mapUrl);
|
||||||
return;
|
return;
|
||||||
|
}).catch((reason) => {
|
||||||
|
reject(reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -116,4 +118,17 @@ export class Room {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isDisconnected(): boolean
|
||||||
|
{
|
||||||
|
const alone = this._search.get('alone');
|
||||||
|
if (alone && alone !== '0' && alone.toLowerCase() !== 'false') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get search(): URLSearchParams {
|
||||||
|
return this._search;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
@ -27,7 +27,7 @@ import {
|
|||||||
SendJitsiJwtMessage,
|
SendJitsiJwtMessage,
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
PingMessage,
|
PingMessage,
|
||||||
SendUserMessage
|
SendUserMessage, BanUserMessage
|
||||||
} from "../Messages/generated/messages_pb"
|
} from "../Messages/generated/messages_pb"
|
||||||
|
|
||||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
@ -41,7 +41,11 @@ import {
|
|||||||
ViewportInterface, WebRtcDisconnectMessageInterface,
|
ViewportInterface, WebRtcDisconnectMessageInterface,
|
||||||
WebRtcSignalReceivedMessageInterface,
|
WebRtcSignalReceivedMessageInterface,
|
||||||
} from "./ConnexionModels";
|
} from "./ConnexionModels";
|
||||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character";
|
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||||
|
import {adminMessagesService} from "./AdminMessagesService";
|
||||||
|
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||||
|
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||||
|
import {connectionManager} from "./ConnectionManager";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -62,9 +66,13 @@ export class RoomConnection implements RoomConnection {
|
|||||||
* @param token A JWT token containing the UUID of the user
|
* @param token A JWT token containing the UUID of the user
|
||||||
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
||||||
*/
|
*/
|
||||||
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) {
|
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null) {
|
||||||
let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://');
|
let url = new URL(PUSHER_URL, window.location.toString()).toString();
|
||||||
url += '/room';
|
url = url.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||||
|
if (!url.endsWith('/')) {
|
||||||
|
url += '/';
|
||||||
|
}
|
||||||
|
url += 'room';
|
||||||
url += '?roomId='+(roomId ?encodeURIComponent(roomId):'');
|
url += '?roomId='+(roomId ?encodeURIComponent(roomId):'');
|
||||||
url += '&token='+(token ?encodeURIComponent(token):'');
|
url += '&token='+(token ?encodeURIComponent(token):'');
|
||||||
url += '&name='+encodeURIComponent(name);
|
url += '&name='+encodeURIComponent(name);
|
||||||
@ -78,6 +86,10 @@ export class RoomConnection implements RoomConnection {
|
|||||||
url += '&left='+Math.floor(viewport.left);
|
url += '&left='+Math.floor(viewport.left);
|
||||||
url += '&right='+Math.floor(viewport.right);
|
url += '&right='+Math.floor(viewport.right);
|
||||||
|
|
||||||
|
if (typeof companion === 'string') {
|
||||||
|
url += '&companion='+encodeURIComponent(companion);
|
||||||
|
}
|
||||||
|
|
||||||
if (RoomConnection.websocketFactory) {
|
if (RoomConnection.websocketFactory) {
|
||||||
this.socket = RoomConnection.websocketFactory(url);
|
this.socket = RoomConnection.websocketFactory(url);
|
||||||
} else {
|
} else {
|
||||||
@ -100,7 +112,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry.
|
// If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry.
|
||||||
if (this.userId === null) {
|
if (this.userId === null && !this.closed) {
|
||||||
this.dispatch(EventMessage.CONNECTING_ERROR, event);
|
this.dispatch(EventMessage.CONNECTING_ERROR, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -140,8 +152,6 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (message.hasRoomjoinedmessage()) {
|
} else if (message.hasRoomjoinedmessage()) {
|
||||||
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
||||||
|
|
||||||
//const users: Array<MessageUserJoined> = roomJoinedMessage.getUserList().map(this.toMessageUserJoined.bind(this));
|
|
||||||
//const groups: Array<GroupCreatedUpdatedMessageInterface> = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage.bind(this));
|
|
||||||
const items: { [itemId: number] : unknown } = {};
|
const items: { [itemId: number] : unknown } = {};
|
||||||
for (const item of roomJoinedMessage.getItemList()) {
|
for (const item of roomJoinedMessage.getItemList()) {
|
||||||
items[item.getItemid()] = JSON.parse(item.getStatejson());
|
items[item.getItemid()] = JSON.parse(item.getStatejson());
|
||||||
@ -150,24 +160,15 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.userId = roomJoinedMessage.getCurrentuserid();
|
this.userId = roomJoinedMessage.getCurrentuserid();
|
||||||
this.tags = roomJoinedMessage.getTagList();
|
this.tags = roomJoinedMessage.getTagList();
|
||||||
|
|
||||||
//console.log('Dispatching CONNECT')
|
|
||||||
this.dispatch(EventMessage.CONNECT, {
|
this.dispatch(EventMessage.CONNECT, {
|
||||||
connection: this,
|
connection: this,
|
||||||
room: {
|
room: {
|
||||||
//users,
|
|
||||||
//groups,
|
|
||||||
items
|
items
|
||||||
} as RoomJoinedMessageInterface
|
} as RoomJoinedMessageInterface
|
||||||
});
|
});
|
||||||
|
} else if (message.hasWorldfullmessage()) {
|
||||||
/*console.log('Dispatching START_ROOM')
|
worldFullMessageStream.onMessage();
|
||||||
this.dispatch(EventMessage.START_ROOM, {
|
this.closed = true;
|
||||||
//users,
|
|
||||||
//groups,
|
|
||||||
items
|
|
||||||
});*/
|
|
||||||
} else if (message.hasErrormessage()) {
|
|
||||||
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage());
|
|
||||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
||||||
@ -185,7 +186,13 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (message.hasSendjitsijwtmessage()) {
|
} else if (message.hasSendjitsijwtmessage()) {
|
||||||
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
|
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
|
||||||
} else if (message.hasSendusermessage()) {
|
} else if (message.hasSendusermessage()) {
|
||||||
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
|
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
||||||
|
} else if (message.hasBanusermessage()) {
|
||||||
|
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
||||||
|
} else if (message.hasWorldfullwarningmessage()) {
|
||||||
|
worldFullWarningStream.onMessage();
|
||||||
|
} else if (message.hasRefreshroommessage()) {
|
||||||
|
//todo: implement a way to notify the user the room was refreshed.
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown message received');
|
throw new Error('Unknown message received');
|
||||||
}
|
}
|
||||||
@ -319,11 +326,14 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const companion = message.getCompanion();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: message.getUserid(),
|
userId: message.getUserid(),
|
||||||
name: message.getName(),
|
name: message.getName(),
|
||||||
characterLayers,
|
characterLayers,
|
||||||
position: ProtobufClientUtils.toPointInterface(position)
|
position: ProtobufClientUtils.toPointInterface(position),
|
||||||
|
companion: companion ? companion.getName() : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,9 +395,6 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.addEventListener('error', callback)
|
this.socket.addEventListener('error', callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public onConnect(callback: (e: Event) => void): void {
|
|
||||||
this.socket.addEventListener('open', callback)
|
|
||||||
}*/
|
|
||||||
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
||||||
//this.socket.addEventListener('open', callback)
|
//this.socket.addEventListener('open', callback)
|
||||||
this.onMessage(EventMessage.CONNECT, callback);
|
this.onMessage(EventMessage.CONNECT, callback);
|
||||||
@ -427,7 +434,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 +445,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,14 +456,16 @@ 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,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
|
public onServerDisconnected(callback: () => void): void {
|
||||||
this.socket.addEventListener('close', (event) => {
|
this.socket.addEventListener('close', (event) => {
|
||||||
if (this.closed === true) {
|
if (this.closed === true || connectionManager.unloading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
||||||
@ -460,11 +473,12 @@ export class RoomConnection implements RoomConnection {
|
|||||||
// Normal closure case
|
// Normal closure case
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback(event);
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUserId(): number|null {
|
public getUserId(): number {
|
||||||
|
if (this.userId === null) throw 'UserId cannot be null!'
|
||||||
return this.userId;
|
return this.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,12 +546,6 @@ export class RoomConnection implements RoomConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public receiveUserMessage(callback: (type: string, message: string) => void) {
|
|
||||||
return this.onMessage(EventMessage.USER_MESSAGE, (message: SendUserMessage) => {
|
|
||||||
callback(message.getType(), message.getMessage());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitGlobalMessage(message: PlayGlobalMessageInterface){
|
public emitGlobalMessage(message: PlayGlobalMessageInterface){
|
||||||
const playGlobalMessage = new PlayGlobalMessage();
|
const playGlobalMessage = new PlayGlobalMessage();
|
||||||
playGlobalMessage.setId(message.id);
|
playGlobalMessage.setId(message.id);
|
||||||
|
14
front/src/Connexion/WorldFullMessageStream.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
class WorldFullMessageStream {
|
||||||
|
|
||||||
|
private _stream:Subject<void> = new Subject();
|
||||||
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
|
onMessage() {
|
||||||
|
this._stream.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const worldFullMessageStream = new WorldFullMessageStream();
|
14
front/src/Connexion/WorldFullWarningStream.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
class WorldFullWarningStream {
|
||||||
|
|
||||||
|
private _stream:Subject<void> = new Subject();
|
||||||
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
|
onMessage() {
|
||||||
|
this._stream.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const worldFullWarningStream = new WorldFullWarningStream();
|
@ -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()
|
|
@ -1,26 +1,32 @@
|
|||||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||||
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost");
|
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
||||||
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
|
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
|
||||||
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "admin.workadventure.localhost");
|
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
|
||||||
const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
|
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
||||||
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
|
const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost";
|
||||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';
|
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||||
|
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||||
|
const TURN_USER: string = process.env.TURN_USER || '';
|
||||||
|
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
||||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
const JITSI_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;
|
||||||
const ZOOM_LEVEL = 1/*3/4*/;
|
const ZOOM_LEVEL = 1/*3/4*/;
|
||||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||||
|
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
API_URL,
|
START_ROOM_URL,
|
||||||
|
PUSHER_URL,
|
||||||
UPLOADER_URL,
|
UPLOADER_URL,
|
||||||
ADMIN_URL,
|
ADMIN_URL,
|
||||||
RESOLUTION,
|
RESOLUTION,
|
||||||
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,
|
||||||
|
221
front/src/Phaser/Companion/Companion.ts
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
|
import Container = Phaser.GameObjects.Container;
|
||||||
|
import { lazyLoadCompanionResource } from "./CompanionTexturesLoadingManager";
|
||||||
|
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||||
|
|
||||||
|
export interface CompanionStatus {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
name: string;
|
||||||
|
moving: boolean;
|
||||||
|
direction: PlayerAnimationDirections;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Companion extends Container {
|
||||||
|
public sprites: Map<string, Sprite>;
|
||||||
|
|
||||||
|
private delta: number;
|
||||||
|
private invisible: boolean;
|
||||||
|
private updateListener: Function;
|
||||||
|
private target: { x: number, y: number, direction: PlayerAnimationDirections };
|
||||||
|
|
||||||
|
private companionName: string;
|
||||||
|
private direction: PlayerAnimationDirections;
|
||||||
|
private animationType: PlayerAnimationTypes;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
|
||||||
|
super(scene, x + 14, y + 4);
|
||||||
|
|
||||||
|
this.sprites = new Map<string, Sprite>();
|
||||||
|
|
||||||
|
this.delta = 0;
|
||||||
|
this.invisible = true;
|
||||||
|
this.target = { x, y, direction: PlayerAnimationDirections.Down };
|
||||||
|
|
||||||
|
this.direction = PlayerAnimationDirections.Down;
|
||||||
|
this.animationType = PlayerAnimationTypes.Idle;
|
||||||
|
|
||||||
|
this.companionName = name;
|
||||||
|
|
||||||
|
texturePromise.then(resource => {
|
||||||
|
this.addResource(resource);
|
||||||
|
this.invisible = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
this.scene.physics.world.enableBody(this);
|
||||||
|
|
||||||
|
this.getBody().setImmovable(true);
|
||||||
|
this.getBody().setCollideWorldBounds(false);
|
||||||
|
this.setSize(16, 16);
|
||||||
|
this.getBody().setSize(16, 16);
|
||||||
|
this.getBody().setOffset(0, 8);
|
||||||
|
|
||||||
|
this.setDepth(-1);
|
||||||
|
|
||||||
|
this.updateListener = this.step.bind(this);
|
||||||
|
this.scene.events.addListener('update', this.updateListener);
|
||||||
|
|
||||||
|
this.scene.add.existing(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTarget(x: number, y: number, direction: PlayerAnimationDirections) {
|
||||||
|
this.target = { x, y: y + 4, direction };
|
||||||
|
}
|
||||||
|
|
||||||
|
public step(time: number, delta: number) {
|
||||||
|
if (typeof this.target === 'undefined') return;
|
||||||
|
|
||||||
|
this.delta += delta;
|
||||||
|
if (this.delta < 128) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.delta = 0;
|
||||||
|
|
||||||
|
const xDist = this.target.x - this.x;
|
||||||
|
const yDist = this.target.y - this.y;
|
||||||
|
|
||||||
|
const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2);
|
||||||
|
|
||||||
|
if (distance < 650) {
|
||||||
|
this.animationType = PlayerAnimationTypes.Idle;
|
||||||
|
this.direction = this.target.direction;
|
||||||
|
|
||||||
|
this.getBody().stop();
|
||||||
|
} else {
|
||||||
|
this.animationType = PlayerAnimationTypes.Walk;
|
||||||
|
|
||||||
|
const xDir = xDist / Math.max(Math.abs(xDist), 1);
|
||||||
|
const yDir = yDist / Math.max(Math.abs(yDist), 1);
|
||||||
|
|
||||||
|
const speed = 256;
|
||||||
|
this.getBody().setVelocity(Math.min(Math.abs(xDist * 2.5), speed) * xDir, Math.min(Math.abs(yDist * 2.5), speed) * yDir);
|
||||||
|
|
||||||
|
if (Math.abs(xDist) > Math.abs(yDist)) {
|
||||||
|
if (xDist < 0) {
|
||||||
|
this.direction = PlayerAnimationDirections.Left;
|
||||||
|
} else {
|
||||||
|
this.direction = PlayerAnimationDirections.Right;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (yDist < 0) {
|
||||||
|
this.direction = PlayerAnimationDirections.Up;
|
||||||
|
} else {
|
||||||
|
this.direction = PlayerAnimationDirections.Down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setDepth(this.y);
|
||||||
|
this.playAnimation(this.direction, this.animationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStatus(): CompanionStatus {
|
||||||
|
const { x, y, direction, animationType, companionName } = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
direction,
|
||||||
|
moving: animationType === PlayerAnimationTypes.Walk,
|
||||||
|
name: companionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void {
|
||||||
|
if (this.invisible) return;
|
||||||
|
|
||||||
|
for (const [resource, sprite] of this.sprites.entries()) {
|
||||||
|
sprite.play(`${resource}-${direction}-${type}`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addResource(resource: string, frame?: string | number): void {
|
||||||
|
const sprite = new Sprite(this.scene, 0, 0, resource, frame);
|
||||||
|
|
||||||
|
this.add(sprite);
|
||||||
|
|
||||||
|
this.getAnimations(resource).forEach(animation => {
|
||||||
|
this.scene.anims.create(animation);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.scene.sys.updateList.add(sprite);
|
||||||
|
this.sprites.set(resource, sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAnimations(resource: string): Phaser.Types.Animations.Animation[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [1]}),
|
||||||
|
frameRate: 10,
|
||||||
|
repeat: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [4]}),
|
||||||
|
frameRate: 10,
|
||||||
|
repeat: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [7]}),
|
||||||
|
frameRate: 10,
|
||||||
|
repeat: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [10]}),
|
||||||
|
frameRate: 10,
|
||||||
|
repeat: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [0, 1, 2]}),
|
||||||
|
frameRate: 15,
|
||||||
|
repeat: -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [3, 4, 5]}),
|
||||||
|
frameRate: 15,
|
||||||
|
repeat: -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [6, 7, 8]}),
|
||||||
|
frameRate: 15,
|
||||||
|
repeat: -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [9, 10, 11]}),
|
||||||
|
frameRate: 15,
|
||||||
|
repeat: -1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBody(): Phaser.Physics.Arcade.Body {
|
||||||
|
const body = this.body;
|
||||||
|
|
||||||
|
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
|
||||||
|
throw new Error('Container does not have arcade body');
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
for (const sprite of this.sprites.values()) {
|
||||||
|
if (this.scene) {
|
||||||
|
this.scene.sys.updateList.remove(sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.scene) {
|
||||||
|
this.scene.events.removeListener('update', this.updateListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
14
front/src/Phaser/Companion/CompanionTextures.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export interface CompanionResourceDescriptionInterface {
|
||||||
|
name: string,
|
||||||
|
img: string,
|
||||||
|
behaviour: "dog" | "cat"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [
|
||||||
|
{ name: "dog1", img: "resources/characters/pipoya/Dog 01-1.png", behaviour: "dog" },
|
||||||
|
{ name: "dog2", img: "resources/characters/pipoya/Dog 01-2.png", behaviour: "dog" },
|
||||||
|
{ name: "dog3", img: "resources/characters/pipoya/Dog 01-3.png", behaviour: "dog" },
|
||||||
|
{ name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" },
|
||||||
|
{ name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" },
|
||||||
|
{ name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" },
|
||||||
|
]
|
@ -0,0 +1,29 @@
|
|||||||
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
|
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
|
||||||
|
|
||||||
|
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
||||||
|
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
||||||
|
lazyLoadCompanionResource(loader, resource.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return COMPANION_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const resource = COMPANION_RESOURCES.find(item => item.name === name);
|
||||||
|
|
||||||
|
if (typeof resource === 'undefined') {
|
||||||
|
return reject(`Texture '${name}' not found!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loader.textureManager.exists(resource.name)) {
|
||||||
|
return resolve(resource.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 });
|
||||||
|
loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name));
|
||||||
|
|
||||||
|
loader.start(); // It's only automatically started during the Scene preload.
|
||||||
|
});
|
||||||
|
}
|
54
front/src/Phaser/Components/Loader.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
||||||
|
|
||||||
|
const LogoNameIndex: string = 'logoLoading';
|
||||||
|
const TextName: string = 'Loading...';
|
||||||
|
const LogoResource: string = 'resources/logos/logo.png';
|
||||||
|
const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59};
|
||||||
|
|
||||||
|
export const addLoader = (scene: Phaser.Scene): void => {
|
||||||
|
// If there is nothing to load, do not display the loader.
|
||||||
|
if (scene.load.list.entries.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let loadingText: Phaser.GameObjects.Text|null = null;
|
||||||
|
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
|
||||||
|
const loadingBarHeight: number = 16;
|
||||||
|
const padding: number = 5;
|
||||||
|
|
||||||
|
const promiseLoadLogoTexture = new Promise<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();
|
||||||
|
progressContainer.fillStyle(0x444444, 0.8);
|
||||||
|
progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2);
|
||||||
|
|
||||||
|
scene.load.on('progress', (value: number) => {
|
||||||
|
progress.clear();
|
||||||
|
progress.fillStyle(0xBBBBBB, 1);
|
||||||
|
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
|
||||||
|
});
|
||||||
|
scene.load.on('complete', () => {
|
||||||
|
if(loadingText){
|
||||||
|
loadingText.destroy();
|
||||||
|
}
|
||||||
|
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||||
|
resLoadingImage.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());
|
||||||
|
@ -1,46 +1,68 @@
|
|||||||
|
|
||||||
|
const IGNORED_KEYS = new Set([
|
||||||
|
'Esc',
|
||||||
|
'Escape',
|
||||||
|
'Alt',
|
||||||
|
'Meta',
|
||||||
|
'Control',
|
||||||
|
'Ctrl',
|
||||||
|
'Space',
|
||||||
|
'Backspace'
|
||||||
|
])
|
||||||
|
|
||||||
export class TextInput extends Phaser.GameObjects.BitmapText {
|
export class TextInput extends Phaser.GameObjects.BitmapText {
|
||||||
private minUnderLineLength = 4;
|
private minUnderLineLength = 4;
|
||||||
private underLine: Phaser.GameObjects.Text;
|
private underLine: Phaser.GameObjects.Text;
|
||||||
|
private domInput = document.createElement('input');
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string, onChange: (text: string) => void) {
|
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string,
|
||||||
|
onChange: (text: string) => void) {
|
||||||
super(scene, x, y, 'main_font', text, 32);
|
super(scene, x, y, 'main_font', text, 32);
|
||||||
this.setOrigin(0.5).setCenterAlign()
|
this.setOrigin(0.5).setCenterAlign();
|
||||||
this.scene.add.existing(this);
|
this.scene.add.existing(this);
|
||||||
|
|
||||||
this.underLine = this.scene.add.text(x, y+1, this.getUnderLineBody(text.length), { fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'})
|
const style = {fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'};
|
||||||
this.underLine.setOrigin(0.5)
|
this.underLine = this.scene.add.text(x, y+1, this.getUnderLineBody(text.length), style);
|
||||||
|
this.underLine.setOrigin(0.5);
|
||||||
|
|
||||||
|
this.domInput.maxLength = maxLength;
|
||||||
this.scene.input.keyboard.on('keydown', (event: KeyboardEvent) => {
|
this.domInput.style.opacity = "0";
|
||||||
if (event.keyCode === 8 && this.text.length > 0) {
|
if (text) {
|
||||||
this.deleteLetter();
|
this.domInput.value = text;
|
||||||
} else if ((event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode <= 90)) && this.text.length < maxLength) {
|
|
||||||
this.addLetter(event.key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.domInput.addEventListener('keydown', event => {
|
||||||
|
if (IGNORED_KEYS.has(event.key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[a-zA-Z0-9:.!&?()+-]/.exec(event.key)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.domInput.addEventListener('input', (event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.text = this.domInput.value;
|
||||||
this.underLine.text = this.getUnderLineBody(this.text.length);
|
this.underLine.text = this.getUnderLineBody(this.text.length);
|
||||||
onChange(this.text);
|
onChange(this.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.body.append(this.domInput);
|
||||||
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUnderLineBody(textLength:number): string {
|
private getUnderLineBody(textLength:number): string {
|
||||||
if (textLength < this.minUnderLineLength) textLength = this.minUnderLineLength;
|
if (textLength < this.minUnderLineLength) textLength = this.minUnderLineLength;
|
||||||
let text = '_______';
|
let text = '_______';
|
||||||
for (let i = this.minUnderLineLength; i < textLength; i++) {
|
for (let i = this.minUnderLineLength; i < textLength; i++) {
|
||||||
text += '__'
|
text += '__';
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteLetter() {
|
|
||||||
this.text = this.text.substr(0, this.text.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private addLetter(letter: string) {
|
|
||||||
this.text += letter;
|
|
||||||
}
|
|
||||||
|
|
||||||
getText(): string {
|
getText(): string {
|
||||||
return this.text;
|
return this.text;
|
||||||
}
|
}
|
||||||
@ -56,4 +78,13 @@ export class TextInput extends Phaser.GameObjects.BitmapText {
|
|||||||
this.underLine.y = y+1;
|
this.underLine.y = y+1;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.domInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
super.destroy();
|
||||||
|
this.domInput.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|