Squashed commit of the following:
commit41748a4036
Merge:3b1d4d63
4991a70b
Author: grégoire parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 21:38:37 2021 +0200 Merge pull request #1327 from thecodingmachine/hotFixErrorCardBack Fix error generated commit4991a70bba
Author: Gregoire Parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 21:34:03 2021 +0200 Fix error generated Don't generate error if file is Invalid commit3b1d4d630c
Merge:f52b4598
02e5860e
Author: grégoire parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 21:03:18 2021 +0200 Merge pull request #1326 from thecodingmachine/HotFixCreateMapFeature Hot fix create map feature commit02e5860e43
Author: Gregoire Parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 20:59:13 2021 +0200 HotFix redirect on production domain of WorkAdventure - Update domain `ADMIN_URL` by `workadventu.re` commitf52b459872
Merge:3d657b4a
3ab069d6
Author: grégoire parant <g.parant@thecodingmachine.com> Date: Mon Aug 2 11:23:16 2021 +0200 Merge pull request #1324 from thecodingmachine/develop Release v1.4.11 commit3ab069d650
Merge:2b748138
9d4ffe54
Author: Kharhamel <Kharhamel@users.noreply.github.com> Date: Fri Jul 30 15:51:07 2021 +0200 Merge pull request #1323 from thecodingmachine/openIDPoc FIX: bomp the node version of pusher commit9d4ffe542c
Author: kharhamel <oognic@gmail.com> Date: Fri Jul 30 15:50:30 2021 +0200 FIX: bomp the node version of pusher commit2b7481383f
Merge:74975ac9
9c803a69
Author: Kharhamel <Kharhamel@users.noreply.github.com> Date: Fri Jul 30 15:48:56 2021 +0200 Merge pull request #1251 from thecodingmachine/openIDPoc POC for the openID connect commit9c803a69ff
Author: kharhamel <oognic@gmail.com> Date: Tue Jul 27 16:37:01 2021 +0200 FEATURE: users can now login via an openID client commit74975ac9d8
Merge:315fe7ca
ebdcf880
Author: Kharhamel <Kharhamel@users.noreply.github.com> Date: Fri Jul 30 14:54:33 2021 +0200 Merge pull request #1322 from thecodingmachine/improveCapacityWarning FEATURE: improved the room capacity warning visuals commitebdcf8804d
Author: kharhamel <oognic@gmail.com> Date: Fri Jul 30 14:08:27 2021 +0200 added admin link to the warning container commit41ac51f291
Author: kharhamel <oognic@gmail.com> Date: Thu Jul 29 18:02:36 2021 +0200 FEATURE: improved the room capacity warning visuals commit315fe7ca82
Author: David Négrier <d.negrier@thecodingmachine.com> Date: Thu Jul 29 17:49:51 2021 +0200 Adding a "font-family" property for text objects. (#1311) - Tiled displays your system fonts. - Computers have different sets of fonts. Therefore, browsers never rely on system fonts - Which means if you select a font in Tiled, it is quite unlikely it will render properly in WorkAdventure To circumvent this problem, in your text object in Tiled, you can now add an additional property: `font-family`. The `font-family` property can contain any "web-font" that can be loaded by your browser. This allows us to use the "Press Start 2P" 8px font in text objects, which renders way better than the default "Sans serif" font of your browser. commit7ffe564e8e
Author: GRL78 <80678534+GRL78@users.noreply.github.com> Date: Thu Jul 29 17:42:16 2021 +0200 Graphic upgrade of the global message console (#1287) * Graphic upgrade of the global message console Fix: error if LoginScene doesn't exist * Rework graphic of global message console * Rework graphic of global message console * Remove console.log commit2a1af2a131
Author: grégoire parant <g.parant@thecodingmachine.com> Date: Thu Jul 29 16:42:31 2021 +0200 PWA service workers (#1319) * PWA services worker - [x] Register service worker of PWA to install WorkAdventure application on desktop and mobile - [x] Create webpage specifique for PWA - [ ] Add register service to save and redirect on a card - [ ] Add possibilities to install PWA for one World (with register token if existing) * Finish PWA strategy to load last map visited * Fix feedback @Kharhamel * Fix feedback @Kharhamel
This commit is contained in:
parent
6ac25d344b
commit
c177f0a1b3
@ -19,3 +19,6 @@ ACME_EMAIL=
|
|||||||
MAX_PER_GROUP=4
|
MAX_PER_GROUP=4
|
||||||
MAX_USERNAME_LENGTH=8
|
MAX_USERNAME_LENGTH=8
|
||||||
|
|
||||||
|
OPID_CLIENT_ID=
|
||||||
|
OPID_CLIENT_SECRET=
|
||||||
|
OPID_CLIENT_ISSUER=
|
||||||
|
3
.github/workflows/continuous_integration.yml
vendored
3
.github/workflows/continuous_integration.yml
vendored
@ -50,6 +50,7 @@ jobs:
|
|||||||
run: yarn run build
|
run: yarn run build
|
||||||
env:
|
env:
|
||||||
PUSHER_URL: "//localhost:8080"
|
PUSHER_URL: "//localhost:8080"
|
||||||
|
ADMIN_URL: "//localhost:80"
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Svelte check"
|
- name: "Svelte check"
|
||||||
@ -81,7 +82,7 @@ jobs:
|
|||||||
- name: "Setup NodeJS"
|
- name: "Setup NodeJS"
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '12.x'
|
node-version: '14.x'
|
||||||
|
|
||||||
- name: Install Protoc
|
- name: Install Protoc
|
||||||
uses: arduino/setup-protoc@v1
|
uses: arduino/setup-protoc@v1
|
||||||
|
1
.github/workflows/push-to-npm.yml
vendored
1
.github/workflows/push-to-npm.yml
vendored
@ -47,6 +47,7 @@ jobs:
|
|||||||
run: yarn run build-typings
|
run: yarn run build-typings
|
||||||
env:
|
env:
|
||||||
PUSHER_URL: "//localhost:8080"
|
PUSHER_URL: "//localhost:8080"
|
||||||
|
ADMIN_URL: "//localhost:80"
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
## Version develop
|
## Version develop
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
- New scripting API features :
|
- New scripting API features:
|
||||||
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file,
|
||||||
|
- Rewrote the way authentication works: the auth jwt token can now contain an email instead of an uuid,
|
||||||
|
- Added an OpenId login flow than can be plugged to any OIDC provider.
|
||||||
## Version 1.4.10
|
## Version 1.4.10
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
@ -27,7 +27,9 @@ class MapFetcher {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!isTiledMap(res.data)) {
|
if (!isTiledMap(res.data)) {
|
||||||
throw new Error("Invalid map format for map " + mapUrl);
|
//TODO fixme
|
||||||
|
//throw new Error("Invalid map format for map " + mapUrl);
|
||||||
|
console.error("Invalid map format for map " + mapUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
|
@ -790,7 +790,7 @@ export class SocketManager {
|
|||||||
if (!room) {
|
if (!room) {
|
||||||
//todo: this should cause the http call to return a 500
|
//todo: this should cause the http call to return a 500
|
||||||
console.error(
|
console.error(
|
||||||
"In sendAdminRoomMessage, could not find room with id '" +
|
"In dispatchWorldFullWarning, could not find room with id '" +
|
||||||
roomId +
|
roomId +
|
||||||
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
|
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,6 @@ RoomConnection.setWebsocketFactory((url: string) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function startOneUser(): Promise<void> {
|
async function startOneUser(): Promise<void> {
|
||||||
await connectionManager.anonymousLogin(true);
|
|
||||||
const onConnect = await connectionManager.connectToRoomSocket(process.env.ROOM_ID ? process.env.ROOM_ID : '_/global/maps.workadventure.localhost/Floor0/floor0.json', 'TEST', ['male3'],
|
const onConnect = await connectionManager.connectToRoomSocket(process.env.ROOM_ID ? process.env.ROOM_ID : '_/global/maps.workadventure.localhost/Floor0/floor0.json', 'TEST', ['male3'],
|
||||||
{
|
{
|
||||||
x: 783,
|
x: 783,
|
||||||
|
@ -53,7 +53,7 @@ services:
|
|||||||
- "traefik.http.routers.front-ssl.service=front"
|
- "traefik.http.routers.front-ssl.service=front"
|
||||||
|
|
||||||
pusher:
|
pusher:
|
||||||
image: thecodingmachine/nodejs:12
|
image: thecodingmachine/nodejs:14
|
||||||
command: yarn dev
|
command: yarn dev
|
||||||
#command: yarn run prod
|
#command: yarn run prod
|
||||||
#command: yarn run profile
|
#command: yarn run profile
|
||||||
@ -66,6 +66,10 @@ services:
|
|||||||
API_URL: back:50051
|
API_URL: back:50051
|
||||||
JITSI_URL: $JITSI_URL
|
JITSI_URL: $JITSI_URL
|
||||||
JITSI_ISS: $JITSI_ISS
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
FRONT_URL: http://localhost
|
||||||
|
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||||
|
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||||
|
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||||
volumes:
|
volumes:
|
||||||
- ./pusher:/usr/src/app
|
- ./pusher:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
|
@ -55,7 +55,7 @@ services:
|
|||||||
- "traefik.http.routers.front-ssl.service=front"
|
- "traefik.http.routers.front-ssl.service=front"
|
||||||
|
|
||||||
pusher:
|
pusher:
|
||||||
image: thecodingmachine/nodejs:12
|
image: thecodingmachine/nodejs:14
|
||||||
command: yarn dev
|
command: yarn dev
|
||||||
environment:
|
environment:
|
||||||
DEBUG: "socket:*"
|
DEBUG: "socket:*"
|
||||||
@ -66,6 +66,10 @@ services:
|
|||||||
API_URL: back:50051
|
API_URL: back:50051
|
||||||
JITSI_URL: $JITSI_URL
|
JITSI_URL: $JITSI_URL
|
||||||
JITSI_ISS: $JITSI_ISS
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
FRONT_URL: http://play.workadventure.localhost
|
||||||
|
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||||
|
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||||
|
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||||
volumes:
|
volumes:
|
||||||
- ./pusher:/usr/src/app
|
- ./pusher:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
|
35
docs/maps/text.md
Normal file
35
docs/maps/text.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{.section-title.accent.text-primary}
|
||||||
|
# Writing text on a map
|
||||||
|
|
||||||
|
## Solution 1: design a specific tileset (recommended)
|
||||||
|
|
||||||
|
If you want to write some text on a map, our recommendation is to create a tileset that contains
|
||||||
|
your text. You will obtain the most pleasant graphical result with this result, since you will be able
|
||||||
|
to control the fonts you use, and you will be able to disable the antialiasing of the font to get a
|
||||||
|
"crispy" result easily.
|
||||||
|
|
||||||
|
## Solution 2: using a "text" object in Tiled
|
||||||
|
|
||||||
|
On "object" layers, Tiled has support for "Text" objects. You can use these objects to add some
|
||||||
|
text on your map.
|
||||||
|
|
||||||
|
WorkAdventure will do its best to display the text properly. However, you need to know that:
|
||||||
|
|
||||||
|
- Tiled displays your system fonts.
|
||||||
|
- Computers have different sets of fonts. Therefore, browsers never rely on system fonts
|
||||||
|
- Which means if you select a font in Tiled, it is quite unlikely it will render properly in WorkAdventure
|
||||||
|
|
||||||
|
To circumvent this problem, in your text object in Tiled, you can add an additional property: `font-family`.
|
||||||
|
|
||||||
|
The `font-family` property can contain any "web-font" that can be loaded by your browser.
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
**Pro-tip:** By default, WorkAdventure uses the **'"Press Start 2P"'** font, which is a great pixelated
|
||||||
|
font that has support for a variety of accents. It renders great when used at *8px* size.
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<figure class="figure">
|
||||||
|
<img src="https://workadventu.re/img/docs/text-object.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||||
|
<figcaption class="figure-caption">The "font-family" property</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
1
front/dist/index.tmpl.html
vendored
1
front/dist/index.tmpl.html
vendored
@ -34,6 +34,7 @@
|
|||||||
<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">
|
||||||
|
|
||||||
<div class="main-container" id="main-container">
|
<div class="main-container" id="main-container">
|
||||||
<!-- Create the editor container -->
|
<!-- Create the editor container -->
|
||||||
<div id="game" class="game">
|
<div id="game" class="game">
|
||||||
|
3
front/dist/resources/html/gameMenu.html
vendored
3
front/dist/resources/html/gameMenu.html
vendored
@ -60,6 +60,9 @@
|
|||||||
<section>
|
<section>
|
||||||
<button id="enableNotification">Enable notifications</button>
|
<button id="enableNotification">Enable notifications</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section hidden>
|
||||||
|
<button id="oidcLogin">Oauth Login</button>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button id="sparkButton">Create map</button>
|
<button id="sparkButton">Create map</button>
|
||||||
</section>
|
</section>
|
||||||
|
18
front/dist/resources/html/warningContainer.html
vendored
18
front/dist/resources/html/warningContainer.html
vendored
@ -1,18 +0,0 @@
|
|||||||
<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>
|
|
62
front/dist/resources/service-worker.html
vendored
Normal file
62
front/dist/resources/service-worker.html
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- TRACK CODE -->
|
||||||
|
<!-- END TRACK CODE -->
|
||||||
|
|
||||||
|
<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="72x72" href="/static/images/favicons/apple-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="/static/images/favicons/apple-icon-76x76.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="/static/images/favicons/apple-icon-114x114.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="/static/images/favicons/apple-icon-120x120.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="/static/images/favicons/apple-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="/static/images/favicons/apple-icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/images/favicons/apple-icon-180x180.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="/static/images/favicons/android-icon-192x192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="/static/images/favicons/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicons/favicon-16x16.png">
|
||||||
|
<meta name="msapplication-TileColor" content="#000000">
|
||||||
|
<meta name="msapplication-TileImage" content="/static/images/favicons/ms-icon-144x144.png">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
|
||||||
|
<title>WorkAdventure PWA</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
font-family: Whitney, Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
}
|
||||||
|
body img{
|
||||||
|
position: absolute;
|
||||||
|
top: calc( 50% - 25px);
|
||||||
|
height: 59px;
|
||||||
|
width: 307px;
|
||||||
|
left: calc( 50% - 150px);
|
||||||
|
}
|
||||||
|
body p{
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: calc( 50% + 50px);
|
||||||
|
left: calc( 50% - 150px);
|
||||||
|
height: 59px;
|
||||||
|
width: 307px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="/static/images/logo.png" alt="WorkAdventure logo"/>
|
||||||
|
<p>Charging your workspace ...</p>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location = localStorage.getItem('lastRoomUrl');
|
||||||
|
}, 4000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
front/dist/resources/service-worker.js
vendored
12
front/dist/resources/service-worker.js
vendored
@ -48,6 +48,14 @@ self.addEventListener('fetch', function(event) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', function(event) {
|
self.addEventListener('wait', function(event) {
|
||||||
//TODO activate service worker
|
//TODO wait
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('update', function(event) {
|
||||||
|
//TODO update
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
//TODO change prompt
|
||||||
});
|
});
|
@ -128,11 +128,12 @@
|
|||||||
"type": "image\/png"
|
"type": "image\/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": "/",
|
"start_url": "/resources/service-worker.html",
|
||||||
"background_color": "#000000",
|
"background_color": "#000000",
|
||||||
"display_override": ["window-control-overlay", "minimal-ui"],
|
"display_override": ["window-control-overlay", "minimal-ui"],
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/",
|
"orientation": "portrait-primary",
|
||||||
|
"scope": "/resources/",
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"shortcuts": [
|
"shortcuts": [
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"@types/mini-css-extract-plugin": "^1.4.3",
|
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||||
"@types/node": "^15.3.0",
|
"@types/node": "^15.3.0",
|
||||||
"@types/quill": "^1.3.7",
|
"@types/quill": "^1.3.7",
|
||||||
|
"@types/uuidv4": "^5.0.0",
|
||||||
"@types/webpack-dev-server": "^3.11.4",
|
"@types/webpack-dev-server": "^3.11.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||||
"@typescript-eslint/parser": "^4.23.0",
|
"@typescript-eslint/parser": "^4.23.0",
|
||||||
@ -53,7 +54,8 @@
|
|||||||
"rxjs": "^6.6.3",
|
"rxjs": "^6.6.3",
|
||||||
"simple-peer": "^9.11.0",
|
"simple-peer": "^9.11.0",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"standardized-audio-context": "^25.2.4"
|
"standardized-audio-context": "^25.2.4",
|
||||||
|
"uuidv4": "^6.2.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "run-p templater serve svelte-check-watch",
|
"start": "run-p templater serve svelte-check-watch",
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
|
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
|
||||||
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
||||||
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
||||||
|
import {warningContainerStore} from "../Stores/MenuStore";
|
||||||
|
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -91,4 +93,7 @@
|
|||||||
{#if $chatVisibilityStore}
|
{#if $chatVisibilityStore}
|
||||||
<Chat></Chat>
|
<Chat></Chat>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $warningContainerStore}
|
||||||
|
<WarningContainer></WarningContainer>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
import InputTextGlobalMessage from "./InputTextGlobalMessage.svelte";
|
import InputTextGlobalMessage from "./InputTextGlobalMessage.svelte";
|
||||||
import UploadAudioGlobalMessage from "./UploadAudioGlobalMessage.svelte";
|
import UploadAudioGlobalMessage from "./UploadAudioGlobalMessage.svelte";
|
||||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import type {Game} from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
|
import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
let inputSendTextActive = true;
|
let inputSendTextActive = true;
|
||||||
let uploadMusicActive = false;
|
let uploadMusicActive = false;
|
||||||
|
let handleSendText: { sendTextMessage(): void };
|
||||||
|
let handleSendAudio: { sendAudioMessage(): Promise<void> };
|
||||||
|
let broadcastToWorld = false;
|
||||||
|
|
||||||
|
function closeConsoleGlobalMessage() {
|
||||||
|
consoleGlobalMessageManagerVisibleStore.set(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e:KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeConsoleGlobalMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function inputSendTextActivate() {
|
function inputSendTextActivate() {
|
||||||
inputSendTextActive = true;
|
inputSendTextActive = true;
|
||||||
@ -17,28 +32,121 @@
|
|||||||
uploadMusicActive = true;
|
uploadMusicActive = true;
|
||||||
inputSendTextActive = false;
|
inputSendTextActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function send() {
|
||||||
|
if (inputSendTextActive) {
|
||||||
|
handleSendText.sendTextMessage();
|
||||||
|
}
|
||||||
|
if (uploadMusicActive) {
|
||||||
|
handleSendAudio.sendAudioMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown}/>
|
||||||
|
|
||||||
<div class="main-console nes-container is-rounded">
|
<div class="console-global-message">
|
||||||
<!-- <div class="console nes-container is-rounded">
|
<div class="menu-console-global-message nes-container is-rounded" transition:fly="{{ x: -1000, duration: 500 }}">
|
||||||
<img class="btn-close" src="resources/logos/send-yellow.svg" alt="Close">
|
<button type="button" class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
|
||||||
</div>-->
|
<button type="button" class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
|
||||||
<div class="main-global-message">
|
|
||||||
<h2> Global Message </h2>
|
|
||||||
<div class="global-message">
|
|
||||||
<div class="menu">
|
|
||||||
<button class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
|
|
||||||
<button class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="main-input">
|
<div class="main-console-global-message nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
|
||||||
|
<div class="title-console-global-message">
|
||||||
|
<h2>Global Message</h2>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={closeConsoleGlobalMessage}><i class="nes-icon close is-small"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="content-console-global-message">
|
||||||
{#if inputSendTextActive}
|
{#if inputSendTextActive}
|
||||||
<InputTextGlobalMessage game={game} gameManager={gameManager}></InputTextGlobalMessage>
|
<InputTextGlobalMessage game={game} gameManager={gameManager} bind:handleSending={handleSendText}/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if uploadMusicActive}
|
{#if uploadMusicActive}
|
||||||
<UploadAudioGlobalMessage game={game} gameManager={gameManager}></UploadAudioGlobalMessage>
|
<UploadAudioGlobalMessage game={game} gameManager={gameManager} bind:handleSending={handleSendAudio}/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="footer-console-global-message">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="nes-checkbox is-dark nes-pointer" bind:checked={broadcastToWorld}>
|
||||||
|
<span>Broadcast to all rooms of the world</span>
|
||||||
|
</label>
|
||||||
|
<button class="nes-btn is-primary" on:click|preventDefault={send}>Send</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
.nes-container {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.console-global-message {
|
||||||
|
top: 20vh;
|
||||||
|
width: 50vw;
|
||||||
|
height: 50vh;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
div.menu-console-global-message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: 180px;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
background-color: #333333;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 136px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.main-console-global-message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: #333333;
|
||||||
|
|
||||||
|
div.title-console-global-message {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 50px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
.nes-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content-console-global-message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: calc(100% - 120px);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer-console-global-message {
|
||||||
|
height: 50px;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||||
import {onMount} from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type {Game} from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import type {GameManager} from "../../Phaser/Game/GameManager";
|
import type { GameManager } from "../../Phaser/Game/GameManager";
|
||||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
||||||
import type {Quill} from "quill";
|
import type { Quill } from "quill";
|
||||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
|
||||||
|
|
||||||
//toolbar
|
//toolbar
|
||||||
export const toolbarOptions = [
|
const toolbarOptions = [
|
||||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||||
['blockquote', 'code-block'],
|
['blockquote', 'code-block'],
|
||||||
|
|
||||||
@ -35,12 +34,31 @@
|
|||||||
export let game: Game;
|
export let game: Game;
|
||||||
export let gameManager: GameManager;
|
export let gameManager: GameManager;
|
||||||
|
|
||||||
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
|
let gameScene = gameManager.getCurrentGameScene(game.findAnyScene());
|
||||||
let quill: Quill;
|
let quill: Quill;
|
||||||
let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
|
let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
|
||||||
|
|
||||||
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||||
|
|
||||||
|
export const handleSending = {
|
||||||
|
sendTextMessage() {
|
||||||
|
if (gameScene == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = quill.getText(0, quill.getLength());
|
||||||
|
|
||||||
|
const GlobalMessage: PlayGlobalMessageInterface = {
|
||||||
|
id: "1", // FIXME: use another ID?
|
||||||
|
message: text,
|
||||||
|
type: MESSAGE_TYPE
|
||||||
|
};
|
||||||
|
|
||||||
|
quill.deleteText(0, quill.getLength());
|
||||||
|
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
||||||
|
disableConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Quill
|
//Quill
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
||||||
@ -48,6 +66,7 @@
|
|||||||
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
quill = new Quill(INPUT_CONSOLE_MESSAGE, {
|
quill = new Quill(INPUT_CONSOLE_MESSAGE, {
|
||||||
|
placeholder: 'Enter your message here...',
|
||||||
theme: 'snow',
|
theme: 'snow',
|
||||||
modules: {
|
modules: {
|
||||||
toolbar: toolbarOptions
|
toolbar: toolbarOptions
|
||||||
@ -66,31 +85,10 @@
|
|||||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||||
consoleGlobalMessageManagerFocusStore.set(false);
|
consoleGlobalMessageManagerFocusStore.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SendTextMessage() {
|
|
||||||
if (gameScene == undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const text = quill.getText(0, quill.getLength());
|
|
||||||
|
|
||||||
const GlobalMessage: PlayGlobalMessageInterface = {
|
|
||||||
id: "1", // FIXME: use another ID?
|
|
||||||
message: text,
|
|
||||||
type: MESSAGE_TYPE
|
|
||||||
};
|
|
||||||
|
|
||||||
quill.deleteText(0, quill.getLength());
|
|
||||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
|
||||||
disableConsole();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<section class="section-input-send-text">
|
<section class="section-input-send-text">
|
||||||
<div class="input-send-text" bind:this={INPUT_CONSOLE_MESSAGE}></div>
|
<div class="input-send-text" bind:this={INPUT_CONSOLE_MESSAGE}></div>
|
||||||
<div class="btn-action">
|
|
||||||
<button class="nes-btn is-primary" on:click|preventDefault={SendTextMessage}>Send</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
import type {Game} from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import type {GameManager} from "../../Phaser/Game/GameManager";
|
import type { GameManager } from "../../Phaser/Game/GameManager";
|
||||||
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore} from "../../Stores/ConsoleGlobalMessageManagerStore";
|
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||||
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
||||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
import uploadFile from "../images/music-file.svg";
|
import uploadFile from "../images/music-file.svg";
|
||||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
|
||||||
|
|
||||||
interface EventTargetFiles extends EventTarget {
|
interface EventTargetFiles extends EventTarget {
|
||||||
files: Array<File>;
|
files: Array<File>;
|
||||||
@ -15,23 +14,23 @@
|
|||||||
export let game: Game;
|
export let game: Game;
|
||||||
export let gameManager: GameManager;
|
export let gameManager: GameManager;
|
||||||
|
|
||||||
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
|
let gameScene = gameManager.getCurrentGameScene(game.findAnyScene());
|
||||||
let fileinput: HTMLInputElement;
|
let fileInput: HTMLInputElement;
|
||||||
let filename: string;
|
let fileName: string;
|
||||||
let filesize: string;
|
let fileSize: string;
|
||||||
let errorfile: boolean;
|
let errorFile: boolean;
|
||||||
|
|
||||||
const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||||
|
|
||||||
|
export const handleSending = {
|
||||||
async function SendAudioMessage() {
|
async sendAudioMessage() {
|
||||||
if (gameScene == undefined) {
|
if (gameScene == undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>("input-send-audio");
|
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>("input-send-audio");
|
||||||
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
||||||
if (!selectedFile) {
|
if (!selectedFile) {
|
||||||
errorfile = true;
|
errorFile = true;
|
||||||
throw 'no file selected';
|
throw 'no file selected';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +47,7 @@
|
|||||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
||||||
disableConsole();
|
disableConsole();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function inputAudioFile(event: Event) {
|
function inputAudioFile(event: Event) {
|
||||||
const eventTarget : EventTargetFiles = (event.target as EventTargetFiles);
|
const eventTarget : EventTargetFiles = (event.target as EventTargetFiles);
|
||||||
@ -60,9 +60,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filename = file.name;
|
fileName = file.name;
|
||||||
filesize = getFileSize(file.size);
|
fileSize = getFileSize(file.size);
|
||||||
errorfile = false;
|
errorFile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileSize(number: number) {
|
function getFileSize(number: number) {
|
||||||
@ -85,46 +85,46 @@
|
|||||||
|
|
||||||
|
|
||||||
<section class="section-input-send-audio">
|
<section class="section-input-send-audio">
|
||||||
<div class="input-send-audio">
|
<img class="nes-pointer" src="{uploadFile}" alt="Upload a file" on:click|preventDefault={ () => {fileInput.click();}}>
|
||||||
<img src="{uploadFile}" alt="Upload a file" on:click|preventDefault={ () => {fileinput.click();}}>
|
{#if fileName !== undefined}
|
||||||
{#if filename != undefined}
|
<p>{fileName} : {fileSize}</p>
|
||||||
<label for="input-send-audio">{filename} : {filesize}</label>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if errorfile}
|
{#if errorFile}
|
||||||
<p class="err">No file selected. You need to upload a file before sending it.</p>
|
<p class="err">No file selected. You need to upload a file before sending it.</p>
|
||||||
{/if}
|
{/if}
|
||||||
<input type="file" id="input-send-audio" bind:this={fileinput} on:change={(e) => {inputAudioFile(e)}}>
|
<input type="file" id="input-send-audio" bind:this={fileInput} on:change={(e) => {inputAudioFile(e)}}>
|
||||||
</div>
|
|
||||||
<div class="btn-action">
|
|
||||||
<button class="nes-btn is-primary" on:click|preventDefault={SendAudioMessage}>Send</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
//UploadAudioGlobalMessage
|
section.section-input-send-audio {
|
||||||
.section-input-send-audio {
|
display: flex;
|
||||||
margin: 10px;
|
flex-direction: column;
|
||||||
}
|
|
||||||
|
|
||||||
.section-input-send-audio .input-send-audio {
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
max-height: 80%;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-input-send-audio #input-send-audio{
|
p {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
color: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
&.err {
|
||||||
|
color: #ce372b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-input-send-audio div.input-send-audio label{
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-input-send-audio div.input-send-audio p.err {
|
|
||||||
color: #ce372b;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-input-send-audio div.input-send-audio img{
|
|
||||||
height: 150px;
|
|
||||||
cursor: url('../../../style/images/cursor_pointer.png'), pointer;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -0,0 +1,37 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import {userIsAdminStore} from "../../Stores/GameStore";
|
||||||
|
import {ADMIN_URL} from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
const upgradeLink = ADMIN_URL+'/pricing';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="warningMain" transition:fly="{{ y: -200, duration: 500 }}">
|
||||||
|
<h2>Warning!</h2>
|
||||||
|
{#if $userIsAdminStore}
|
||||||
|
<p>This world is close to its limit!. You can upgrade its capacity <a href="{upgradeLink}" target="_blank">here</a></p>
|
||||||
|
{:else}
|
||||||
|
<p>This world is close to its limit!</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
main.warningMain {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: red;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
font-family: Lato;
|
||||||
|
min-width: 300px;
|
||||||
|
opacity: 0.9;
|
||||||
|
z-index: 2;
|
||||||
|
h2 {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,6 +6,7 @@ import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
|
|||||||
import { localUserStore } from "./LocalUserStore";
|
import { localUserStore } from "./LocalUserStore";
|
||||||
import { CharacterTexture, LocalUser } from "./LocalUser";
|
import { CharacterTexture, LocalUser } from "./LocalUser";
|
||||||
import { Room } from "./Room";
|
import { Room } from "./Room";
|
||||||
|
import { _ServiceWorker } from "../Network/ServiceWorker";
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!: LocalUser;
|
private localUser!: LocalUser;
|
||||||
@ -13,6 +14,9 @@ class ConnectionManager {
|
|||||||
private connexionType?: GameConnexionTypes;
|
private connexionType?: GameConnexionTypes;
|
||||||
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
||||||
private _unloading: boolean = false;
|
private _unloading: boolean = false;
|
||||||
|
private authToken: string | null = null;
|
||||||
|
|
||||||
|
private serviceWorker?: _ServiceWorker;
|
||||||
|
|
||||||
get unloading() {
|
get unloading() {
|
||||||
return this._unloading;
|
return this._unloading;
|
||||||
@ -24,23 +28,58 @@ class ConnectionManager {
|
|||||||
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout);
|
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadOpenIDScreen() {
|
||||||
|
localUserStore.setAuthToken(null);
|
||||||
|
const state = localUserStore.generateState();
|
||||||
|
const nonce = localUserStore.generateNonce();
|
||||||
|
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout() {
|
||||||
|
localUserStore.setAuthToken(null);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
public async initGameConnexion(): Promise<Room> {
|
public async initGameConnexion(): Promise<Room> {
|
||||||
const connexionType = urlManager.getGameConnexionType();
|
const connexionType = urlManager.getGameConnexionType();
|
||||||
this.connexionType = connexionType;
|
this.connexionType = connexionType;
|
||||||
if (connexionType === GameConnexionTypes.register) {
|
let room: Room | null = null;
|
||||||
|
if (connexionType === GameConnexionTypes.jwt) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const code = urlParams.get("code");
|
||||||
|
const state = urlParams.get("state");
|
||||||
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
|
throw "Could not validate state!";
|
||||||
|
}
|
||||||
|
if (!code) {
|
||||||
|
throw "No Auth code provided";
|
||||||
|
}
|
||||||
|
const nonce = localUserStore.getNonce();
|
||||||
|
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce } }).then(
|
||||||
|
(res) => res.data
|
||||||
|
);
|
||||||
|
localUserStore.setAuthToken(authToken);
|
||||||
|
this.authToken = authToken;
|
||||||
|
room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||||
|
urlManager.pushRoomIdToUrl(room);
|
||||||
|
} else if (connexionType === GameConnexionTypes.register) {
|
||||||
|
//@deprecated
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
||||||
(res) => res.data
|
(res) => res.data
|
||||||
);
|
);
|
||||||
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
this.localUser = new LocalUser(data.userUuid, data.textures);
|
||||||
|
this.authToken = data.authToken;
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
|
localUserStore.setAuthToken(this.authToken);
|
||||||
|
|
||||||
const roomUrl = data.roomUrl;
|
const roomUrl = data.roomUrl;
|
||||||
|
|
||||||
const room = await Room.createRoom(
|
room = await Room.createRoom(
|
||||||
new URL(
|
new URL(
|
||||||
window.location.protocol +
|
window.location.protocol +
|
||||||
"//" +
|
"//" +
|
||||||
@ -51,30 +90,17 @@ class ConnectionManager {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
urlManager.pushRoomIdToUrl(room);
|
urlManager.pushRoomIdToUrl(room);
|
||||||
return Promise.resolve(room);
|
|
||||||
} else if (
|
} else if (
|
||||||
connexionType === GameConnexionTypes.organization ||
|
connexionType === GameConnexionTypes.organization ||
|
||||||
connexionType === GameConnexionTypes.anonymous ||
|
connexionType === GameConnexionTypes.anonymous ||
|
||||||
connexionType === GameConnexionTypes.empty
|
connexionType === GameConnexionTypes.empty
|
||||||
) {
|
) {
|
||||||
let localUser = localUserStore.getLocalUser();
|
this.authToken = localUserStore.getAuthToken();
|
||||||
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
|
//todo: add here some kind of warning if authToken has expired.
|
||||||
this.localUser = localUser;
|
if (!this.authToken) {
|
||||||
try {
|
|
||||||
await this.verifyToken(localUser.jwtToken);
|
|
||||||
} catch (e) {
|
|
||||||
// If the token is invalid, let's generate an anonymous one.
|
|
||||||
console.error("JWT token invalid. Did it expire? Login anonymously instead.");
|
|
||||||
await this.anonymousLogin();
|
await this.anonymousLogin();
|
||||||
}
|
}
|
||||||
} else {
|
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||||
await this.anonymousLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
localUser = localUserStore.getLocalUser();
|
|
||||||
if (!localUser) {
|
|
||||||
throw "Error to store local user data";
|
|
||||||
}
|
|
||||||
|
|
||||||
let roomPath: string;
|
let roomPath: string;
|
||||||
if (connexionType === GameConnexionTypes.empty) {
|
if (connexionType === GameConnexionTypes.empty) {
|
||||||
@ -90,44 +116,44 @@ class ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//get detail map for anonymous login and set texture in local storage
|
//get detail map for anonymous login and set texture in local storage
|
||||||
const room = await Room.createRoom(new URL(roomPath));
|
room = await Room.createRoom(new URL(roomPath));
|
||||||
if (room.textures != undefined && room.textures.length > 0) {
|
if (room.textures != undefined && room.textures.length > 0) {
|
||||||
//check if texture was changed
|
//check if texture was changed
|
||||||
if (localUser.textures.length === 0) {
|
if (this.localUser.textures.length === 0) {
|
||||||
localUser.textures = room.textures;
|
this.localUser.textures = room.textures;
|
||||||
} else {
|
} else {
|
||||||
room.textures.forEach((newTexture) => {
|
room.textures.forEach((newTexture) => {
|
||||||
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
||||||
if (localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localUser?.textures.push(newTexture);
|
this.localUser.textures.push(newTexture);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.localUser = localUser;
|
localUserStore.saveUser(this.localUser);
|
||||||
localUserStore.saveUser(localUser);
|
|
||||||
}
|
}
|
||||||
return Promise.resolve(room);
|
|
||||||
}
|
}
|
||||||
|
if (room == undefined) {
|
||||||
return Promise.reject(new Error("Invalid URL"));
|
return Promise.reject(new Error("Invalid URL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async verifyToken(token: string): Promise<void> {
|
this.serviceWorker = new _ServiceWorker();
|
||||||
await Axios.get(`${PUSHER_URL}/verify`, { params: { token } });
|
return Promise.resolve(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||||
const data = await Axios.post(`${PUSHER_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, []);
|
||||||
|
this.authToken = data.authToken;
|
||||||
if (!isBenchmark) {
|
if (!isBenchmark) {
|
||||||
// In benchmark, we don't have a local storage.
|
// In benchmark, we don't have a local storage.
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
|
localUserStore.setAuthToken(this.authToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public initBenchmark(): void {
|
public initBenchmark(): void {
|
||||||
this.localUser = new LocalUser("", "test", []);
|
this.localUser = new LocalUser("", []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectToRoomSocket(
|
public connectToRoomSocket(
|
||||||
@ -140,7 +166,7 @@ class ConnectionManager {
|
|||||||
): Promise<OnConnectInterface> {
|
): Promise<OnConnectInterface> {
|
||||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||||
const connection = new RoomConnection(
|
const connection = new RoomConnection(
|
||||||
this.localUser.jwtToken,
|
this.authToken,
|
||||||
roomUrl,
|
roomUrl,
|
||||||
name,
|
name,
|
||||||
characterLayers,
|
characterLayers,
|
||||||
@ -148,6 +174,7 @@ class ConnectionManager {
|
|||||||
viewport,
|
viewport,
|
||||||
companion
|
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);
|
||||||
@ -166,6 +193,9 @@ class ConnectionManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
connection.onConnect((connect: OnConnectInterface) => {
|
connection.onConnect((connect: OnConnectInterface) => {
|
||||||
|
//save last room url connected
|
||||||
|
localUserStore.setLastRoomUrl(roomUrl);
|
||||||
|
|
||||||
resolve(connect);
|
resolve(connect);
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {MAX_USERNAME_LENGTH} from "../Enum/EnvironmentVariable";
|
import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export interface CharacterTexture {
|
export interface CharacterTexture {
|
||||||
id: number,
|
id: number;
|
||||||
level: number,
|
level: number;
|
||||||
url: string,
|
url: string;
|
||||||
rights: string
|
rights: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||||
@ -24,6 +24,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LocalUser {
|
export class LocalUser {
|
||||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
|
constructor(public readonly uuid: string, public textures: CharacterTexture[]) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,60 +1,65 @@
|
|||||||
import {areCharacterLayersValid, isUserNameValid, LocalUser} from "./LocalUser";
|
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
const playerNameKey = 'playerName';
|
const playerNameKey = "playerName";
|
||||||
const selectedPlayerKey = 'selectedPlayer';
|
const selectedPlayerKey = "selectedPlayer";
|
||||||
const customCursorPositionKey = 'customCursorPosition';
|
const customCursorPositionKey = "customCursorPosition";
|
||||||
const characterLayersKey = 'characterLayers';
|
const characterLayersKey = "characterLayers";
|
||||||
const companionKey = 'companion';
|
const companionKey = "companion";
|
||||||
const gameQualityKey = 'gameQuality';
|
const gameQualityKey = "gameQuality";
|
||||||
const videoQualityKey = 'videoQuality';
|
const videoQualityKey = "videoQuality";
|
||||||
const audioPlayerVolumeKey = 'audioVolume';
|
const audioPlayerVolumeKey = "audioVolume";
|
||||||
const audioPlayerMuteKey = 'audioMute';
|
const audioPlayerMuteKey = "audioMute";
|
||||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
||||||
const fullscreenKey = 'fullscreen';
|
const fullscreenKey = "fullscreen";
|
||||||
|
const lastRoomUrl = "lastRoomUrl";
|
||||||
|
const authToken = "authToken";
|
||||||
|
const state = "state";
|
||||||
|
const nonce = "nonce";
|
||||||
|
|
||||||
class LocalUserStore {
|
class LocalUserStore {
|
||||||
saveUser(localUser: LocalUser) {
|
saveUser(localUser: LocalUser) {
|
||||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
localStorage.setItem("localUser", JSON.stringify(localUser));
|
||||||
}
|
}
|
||||||
getLocalUser(): LocalUser|null {
|
getLocalUser(): LocalUser | null {
|
||||||
const data = localStorage.getItem('localUser');
|
const data = localStorage.getItem("localUser");
|
||||||
return data ? JSON.parse(data) : null;
|
return data ? JSON.parse(data) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name:string): void {
|
setName(name: string): void {
|
||||||
localStorage.setItem(playerNameKey, name);
|
localStorage.setItem(playerNameKey, name);
|
||||||
}
|
}
|
||||||
getName(): string|null {
|
getName(): string | null {
|
||||||
const value = localStorage.getItem(playerNameKey) || '';
|
const value = localStorage.getItem(playerNameKey) || "";
|
||||||
return isUserNameValid(value) ? value : null;
|
return isUserNameValid(value) ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||||
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
localStorage.setItem(selectedPlayerKey, "" + playerCharacterIndex);
|
||||||
}
|
}
|
||||||
getPlayerCharacterIndex(): number {
|
getPlayerCharacterIndex(): number {
|
||||||
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
return parseInt(localStorage.getItem(selectedPlayerKey) || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
setCustomCursorPosition(activeRow: number, selectedLayers: number[]): void {
|
||||||
localStorage.setItem(customCursorPositionKey, 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(localStorage.getItem(customCursorPositionKey) || "null");
|
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCharacterLayers(layers: string[]): void {
|
setCharacterLayers(layers: string[]): void {
|
||||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||||
}
|
}
|
||||||
getCharacterLayers(): string[]|null {
|
getCharacterLayers(): string[] | null {
|
||||||
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||||
return areCharacterLayersValid(value) ? value : null;
|
return areCharacterLayersValid(value) ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCompanion(companion: string|null): void {
|
setCompanion(companion: string | null): void {
|
||||||
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
||||||
}
|
}
|
||||||
getCompanion(): string|null {
|
getCompanion(): string | null {
|
||||||
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
||||||
|
|
||||||
if (typeof companion !== "string" || companion === "") {
|
if (typeof companion !== "string" || companion === "") {
|
||||||
@ -68,45 +73,82 @@ class LocalUserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setGameQualityValue(value: number): void {
|
setGameQualityValue(value: number): void {
|
||||||
localStorage.setItem(gameQualityKey, '' + value);
|
localStorage.setItem(gameQualityKey, "" + value);
|
||||||
}
|
}
|
||||||
getGameQualityValue(): number {
|
getGameQualityValue(): number {
|
||||||
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
return parseInt(localStorage.getItem(gameQualityKey) || "60");
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoQualityValue(value: number): void {
|
setVideoQualityValue(value: number): void {
|
||||||
localStorage.setItem(videoQualityKey, '' + value);
|
localStorage.setItem(videoQualityKey, "" + value);
|
||||||
}
|
}
|
||||||
getVideoQualityValue(): number {
|
getVideoQualityValue(): number {
|
||||||
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
return parseInt(localStorage.getItem(videoQualityKey) || "20");
|
||||||
}
|
}
|
||||||
|
|
||||||
setAudioPlayerVolume(value: number): void {
|
setAudioPlayerVolume(value: number): void {
|
||||||
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
localStorage.setItem(audioPlayerVolumeKey, "" + value);
|
||||||
}
|
}
|
||||||
getAudioPlayerVolume(): number {
|
getAudioPlayerVolume(): number {
|
||||||
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
setAudioPlayerMuted(value: boolean): void {
|
setAudioPlayerMuted(value: boolean): void {
|
||||||
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||||
}
|
}
|
||||||
getAudioPlayerMuted(): boolean {
|
getAudioPlayerMuted(): boolean {
|
||||||
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
return localStorage.getItem(audioPlayerMuteKey) === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
setHelpCameraSettingsShown(): void {
|
setHelpCameraSettingsShown(): void {
|
||||||
localStorage.setItem(helpCameraSettingsShown, '1');
|
localStorage.setItem(helpCameraSettingsShown, "1");
|
||||||
}
|
}
|
||||||
getHelpCameraSettingsShown(): boolean {
|
getHelpCameraSettingsShown(): boolean {
|
||||||
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
return localStorage.getItem(helpCameraSettingsShown) === "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullscreen(value: boolean): void {
|
setFullscreen(value: boolean): void {
|
||||||
localStorage.setItem(fullscreenKey, value.toString());
|
localStorage.setItem(fullscreenKey, value.toString());
|
||||||
}
|
}
|
||||||
getFullscreen(): boolean {
|
getFullscreen(): boolean {
|
||||||
return localStorage.getItem(fullscreenKey) === 'true';
|
return localStorage.getItem(fullscreenKey) === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastRoomUrl(roomUrl: string): void {
|
||||||
|
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||||
|
}
|
||||||
|
getLastRoomUrl(): string {
|
||||||
|
return localStorage.getItem(lastRoomUrl) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
setAuthToken(value: string | null) {
|
||||||
|
value ? localStorage.setItem(authToken, value) : localStorage.removeItem(authToken);
|
||||||
|
}
|
||||||
|
getAuthToken(): string | null {
|
||||||
|
return localStorage.getItem(authToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateState(): string {
|
||||||
|
const newState = uuidv4();
|
||||||
|
localStorage.setItem(state, newState);
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyState(value: string): boolean {
|
||||||
|
const oldValue = localStorage.getItem(state);
|
||||||
|
localStorage.removeItem(state);
|
||||||
|
return oldValue === value;
|
||||||
|
}
|
||||||
|
generateNonce(): string {
|
||||||
|
const newNonce = uuidv4();
|
||||||
|
localStorage.setItem(nonce, newNonce);
|
||||||
|
return newNonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNonce(): string | null {
|
||||||
|
const oldValue = localStorage.getItem(nonce);
|
||||||
|
localStorage.removeItem(nonce);
|
||||||
|
return oldValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,8 @@ import {
|
|||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
SendUserMessage,
|
SendUserMessage,
|
||||||
BanUserMessage,
|
BanUserMessage,
|
||||||
VariableMessage, ErrorMessage,
|
VariableMessage,
|
||||||
|
ErrorMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
||||||
@ -54,9 +55,9 @@ import {
|
|||||||
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
||||||
import { adminMessagesService } from "./AdminMessagesService";
|
import { adminMessagesService } from "./AdminMessagesService";
|
||||||
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
||||||
import { worldFullWarningStream } from "./WorldFullWarningStream";
|
|
||||||
import { connectionManager } from "./ConnectionManager";
|
import { connectionManager } from "./ConnectionManager";
|
||||||
import { emoteEventStream } from "./EmoteEventStream";
|
import { emoteEventStream } from "./EmoteEventStream";
|
||||||
|
import { warningContainerStore } from "../Stores/MenuStore";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param token A JWT token containing the UUID of the user
|
* @param token A JWT token containing the email of the user
|
||||||
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
|
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
@ -167,7 +168,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
||||||
} else if (subMessage.hasErrormessage()) {
|
} else if (subMessage.hasErrormessage()) {
|
||||||
const errorMessage = subMessage.getErrormessage() as ErrorMessage;
|
const errorMessage = subMessage.getErrormessage() as ErrorMessage;
|
||||||
console.error('An error occurred server side: '+errorMessage.getMessage());
|
console.error("An error occurred server side: " + errorMessage.getMessage());
|
||||||
} else if (subMessage.hasVariablemessage()) {
|
} else if (subMessage.hasVariablemessage()) {
|
||||||
event = EventMessage.SET_VARIABLE;
|
event = EventMessage.SET_VARIABLE;
|
||||||
payload = subMessage.getVariablemessage();
|
payload = subMessage.getVariablemessage();
|
||||||
@ -192,7 +193,14 @@ export class RoomConnection implements RoomConnection {
|
|||||||
try {
|
try {
|
||||||
variables.set(variable.getName(), JSON.parse(variable.getValue()));
|
variables.set(variable.getName(), JSON.parse(variable.getValue()));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Unable to unserialize value received from server for variable "'+variable.getName()+'". Value received: "'+variable.getValue()+'". Error: ', e);
|
console.error(
|
||||||
|
'Unable to unserialize value received from server for variable "' +
|
||||||
|
variable.getName() +
|
||||||
|
'". Value received: "' +
|
||||||
|
variable.getValue() +
|
||||||
|
'". Error: ',
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +217,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (message.hasWorldfullmessage()) {
|
} else if (message.hasWorldfullmessage()) {
|
||||||
worldFullMessageStream.onMessage();
|
worldFullMessageStream.onMessage();
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
|
} else if (message.hasTokenexpiredmessage()) {
|
||||||
|
connectionManager.loadOpenIDScreen();
|
||||||
|
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
|
||||||
} else if (message.hasWorldconnexionmessage()) {
|
} else if (message.hasWorldconnexionmessage()) {
|
||||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
@ -236,7 +247,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (message.hasBanusermessage()) {
|
} else if (message.hasBanusermessage()) {
|
||||||
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
|
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
|
||||||
} else if (message.hasWorldfullwarningmessage()) {
|
} else if (message.hasWorldfullwarningmessage()) {
|
||||||
worldFullWarningStream.onMessage();
|
warningContainerStore.activateWarningContainer();
|
||||||
} else if (message.hasRefreshroommessage()) {
|
} else if (message.hasRefreshroommessage()) {
|
||||||
//todo: implement a way to notify the user the room was refreshed.
|
//todo: implement a way to notify the user the room was refreshed.
|
||||||
} else {
|
} else {
|
||||||
@ -659,7 +670,14 @@ export class RoomConnection implements RoomConnection {
|
|||||||
try {
|
try {
|
||||||
value = JSON.parse(serializedValue);
|
value = JSON.parse(serializedValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Unable to unserialize value received from server for variable "'+name+'". Value received: "'+serializedValue+'". Error: ', e);
|
console.error(
|
||||||
|
'Unable to unserialize value received from server for variable "' +
|
||||||
|
name +
|
||||||
|
'". Value received: "' +
|
||||||
|
serializedValue +
|
||||||
|
'". Error: ',
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(name, value);
|
callback(name, value);
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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,22 +1,24 @@
|
|||||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||||
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
const START_ROOM_URL: string =
|
||||||
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
|
process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||||
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
||||||
|
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
||||||
|
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
||||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
||||||
const TURN_USER: string = process.env.TURN_USER || '';
|
const TURN_USER: string = process.env.TURN_USER || "";
|
||||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
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 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 const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8;
|
||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
|
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
||||||
|
|
||||||
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
|
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
@ -32,5 +34,5 @@ export {
|
|||||||
TURN_USER,
|
TURN_USER,
|
||||||
TURN_PASSWORD,
|
TURN_PASSWORD,
|
||||||
JITSI_URL,
|
JITSI_URL,
|
||||||
JITSI_PRIVATE_MODE
|
JITSI_PRIVATE_MODE,
|
||||||
}
|
};
|
||||||
|
20
front/src/Network/ServiceWorker.ts
Normal file
20
front/src/Network/ServiceWorker.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export class _ServiceWorker {
|
||||||
|
constructor() {
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("/resources/service-worker.js")
|
||||||
|
.then((serviceWorker) => {
|
||||||
|
console.info("Service Worker registered: ", serviceWorker);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error registering the Service Worker: ", error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,43 @@
|
|||||||
import type {ITiledMapObject} from "../Map/ITiledMap";
|
import type { ITiledMapObject } from "../Map/ITiledMap";
|
||||||
import type {GameScene} from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
|
import { type } from "os";
|
||||||
|
|
||||||
export class TextUtils {
|
export class TextUtils {
|
||||||
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
|
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
|
||||||
if (object.text === undefined) {
|
if (object.text === undefined) {
|
||||||
throw new Error('This object has not textual representation.');
|
throw new Error("This object has not textual representation.");
|
||||||
}
|
}
|
||||||
const options: {
|
const options: {
|
||||||
fontStyle?: string,
|
fontStyle?: string;
|
||||||
fontSize?: string,
|
fontSize?: string;
|
||||||
fontFamily?: string,
|
fontFamily?: string;
|
||||||
color?: string,
|
color?: string;
|
||||||
align?: string,
|
align?: string;
|
||||||
wordWrap?: {
|
wordWrap?: {
|
||||||
width: number,
|
width: number;
|
||||||
useAdvancedWrap?: boolean
|
useAdvancedWrap?: boolean;
|
||||||
}
|
};
|
||||||
} = {};
|
} = {};
|
||||||
if (object.text.italic) {
|
if (object.text.italic) {
|
||||||
options.fontStyle = 'italic';
|
options.fontStyle = "italic";
|
||||||
}
|
}
|
||||||
// Note: there is no support for "strikeout" and "underline"
|
// Note: there is no support for "strikeout" and "underline"
|
||||||
let fontSize: number = 16;
|
let fontSize: number = 16;
|
||||||
if (object.text.pixelsize) {
|
if (object.text.pixelsize) {
|
||||||
fontSize = object.text.pixelsize;
|
fontSize = object.text.pixelsize;
|
||||||
}
|
}
|
||||||
options.fontSize = fontSize + 'px';
|
options.fontSize = fontSize + "px";
|
||||||
if (object.text.fontfamily) {
|
if (object.text.fontfamily) {
|
||||||
options.fontFamily = '"'+object.text.fontfamily+'"';
|
options.fontFamily = '"' + object.text.fontfamily + '"';
|
||||||
}
|
}
|
||||||
let color = '#000000';
|
if (object.properties !== undefined) {
|
||||||
|
for (const property of object.properties) {
|
||||||
|
if (property.name === "font-family" && typeof property.value === "string") {
|
||||||
|
options.fontFamily = property.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let color = "#000000";
|
||||||
if (object.text.color !== undefined) {
|
if (object.text.color !== undefined) {
|
||||||
color = object.text.color;
|
color = object.text.color;
|
||||||
}
|
}
|
||||||
@ -38,7 +46,7 @@ export class TextUtils {
|
|||||||
options.wordWrap = {
|
options.wordWrap = {
|
||||||
width: object.width,
|
width: object.width,
|
||||||
//useAdvancedWrap: true
|
//useAdvancedWrap: true
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (object.text.halign !== undefined) {
|
if (object.text.halign !== undefined) {
|
||||||
options.align = object.text.halign;
|
options.align = object.text.halign;
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
export const warningContainerKey = 'warningContainer';
|
|
||||||
export const warningContainerHtml = 'resources/html/warningContainer.html';
|
|
||||||
|
|
||||||
export class WarningContainer extends Phaser.GameObjects.DOMElement {
|
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene) {
|
|
||||||
super(scene, 100, 0);
|
|
||||||
this.setOrigin(0, 0);
|
|
||||||
this.createFromCache(warningContainerKey);
|
|
||||||
this.scene.add.existing(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable";
|
||||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import {ResizableScene} from "../Login/ResizableScene";
|
import { ResizableScene } from "../Login/ResizableScene";
|
||||||
|
|
||||||
const Events = Phaser.Core.Events;
|
const Events = Phaser.Core.Events;
|
||||||
|
|
||||||
@ -14,10 +14,8 @@ const Events = Phaser.Core.Events;
|
|||||||
* It also automatically calls "onResize" on any scenes extending ResizableScene.
|
* It also automatically calls "onResize" on any scenes extending ResizableScene.
|
||||||
*/
|
*/
|
||||||
export class Game extends Phaser.Game {
|
export class Game extends Phaser.Game {
|
||||||
|
|
||||||
private _isDirty = false;
|
private _isDirty = false;
|
||||||
|
|
||||||
|
|
||||||
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
|
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
|
||||||
super(GameConfig);
|
super(GameConfig);
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ export class Game extends Phaser.Game {
|
|||||||
scene.onResize();
|
scene.onResize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
/*window.addEventListener('resize', (event) => {
|
/*window.addEventListener('resize', (event) => {
|
||||||
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
||||||
@ -39,11 +37,9 @@ export class Game extends Phaser.Game {
|
|||||||
});*/
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(time: number, delta: number)
|
public step(time: number, delta: number) {
|
||||||
{
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (this.pendingDestroy)
|
if (this.pendingDestroy) {
|
||||||
{
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return this.runDestroy();
|
return this.runDestroy();
|
||||||
}
|
}
|
||||||
@ -100,15 +96,17 @@ export class Game extends Phaser.Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loop through the scenes in forward order
|
// Loop through the scenes in forward order
|
||||||
for (let i = 0; i < this.scene.scenes.length; i++)
|
for (let i = 0; i < this.scene.scenes.length; i++) {
|
||||||
{
|
|
||||||
const scene = this.scene.scenes[i];
|
const scene = this.scene.scenes[i];
|
||||||
const sys = scene.sys;
|
const sys = scene.sys;
|
||||||
|
|
||||||
if (sys.settings.visible && sys.settings.status >= Phaser.Scenes.LOADING && sys.settings.status < Phaser.Scenes.SLEEPING)
|
if (
|
||||||
{
|
sys.settings.visible &&
|
||||||
|
sys.settings.status >= Phaser.Scenes.LOADING &&
|
||||||
|
sys.settings.status < Phaser.Scenes.SLEEPING
|
||||||
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if(typeof scene.isDirty === 'function') {
|
if (typeof scene.isDirty === "function") {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const isDirty = scene.isDirty() || scene.tweens.getAllTweens().length > 0;
|
const isDirty = scene.isDirty() || scene.tweens.getAllTweens().length > 0;
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
@ -129,4 +127,11 @@ export class Game extends Phaser.Game {
|
|||||||
public markDirty(): void {
|
public markDirty(): void {
|
||||||
this._isDirty = true;
|
this._isDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first scene found in the game
|
||||||
|
*/
|
||||||
|
public findAnyScene(): Phaser.Scene {
|
||||||
|
return this.scene.getScenes()[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,7 @@ import { SharedVariablesManager } from "./SharedVariablesManager";
|
|||||||
import { playersStore } from "../../Stores/PlayersStore";
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||||
import Tileset = Phaser.Tilemaps.Tileset;
|
import Tileset = Phaser.Tilemaps.Tileset;
|
||||||
|
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -605,6 +606,8 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
playersStore.connectToRoomConnection(this.connection);
|
playersStore.connectToRoomConnection(this.connection);
|
||||||
|
|
||||||
|
userIsAdminStore.set(this.connection.hasTag("admin"));
|
||||||
|
|
||||||
this.connection.onUserJoins((message: MessageUserJoined) => {
|
this.connection.onUserJoins((message: MessageUserJoined) => {
|
||||||
const userMessage: AddPlayerInterface = {
|
const userMessage: AddPlayerInterface = {
|
||||||
userId: message.userId,
|
userId: message.userId,
|
||||||
|
@ -6,8 +6,6 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
|||||||
import { gameReportKey, gameReportRessource, ReportMenu } from "./ReportMenu";
|
import { gameReportKey, gameReportRessource, ReportMenu } from "./ReportMenu";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import { GameConnexionTypes } from "../../Url/UrlManager";
|
import { GameConnexionTypes } from "../../Url/UrlManager";
|
||||||
import { WarningContainer, warningContainerHtml, warningContainerKey } from "../Components/WarningContainer";
|
|
||||||
import { worldFullWarningStream } from "../../Connexion/WorldFullWarningStream";
|
|
||||||
import { menuIconVisible } from "../../Stores/MenuStore";
|
import { menuIconVisible } from "../../Stores/MenuStore";
|
||||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||||
@ -21,6 +19,7 @@ import { get } from "svelte/store";
|
|||||||
import { playersStore } from "../../Stores/PlayersStore";
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||||
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export const MenuSceneName = "MenuScene";
|
export const MenuSceneName = "MenuScene";
|
||||||
const gameMenuKey = "gameMenu";
|
const gameMenuKey = "gameMenu";
|
||||||
@ -45,8 +44,6 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
private gameQualityValue: number;
|
private gameQualityValue: number;
|
||||||
private videoQualityValue: number;
|
private videoQualityValue: number;
|
||||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
private menuButton!: Phaser.GameObjects.DOMElement;
|
||||||
private warningContainer: WarningContainer | null = null;
|
|
||||||
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
|
||||||
private subscriptions = new Subscription();
|
private subscriptions = new Subscription();
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ key: MenuSceneName });
|
super({ key: MenuSceneName });
|
||||||
@ -91,7 +88,6 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
this.load.html(gameSettingsMenuKey, "resources/html/gameQualityMenu.html");
|
this.load.html(gameSettingsMenuKey, "resources/html/gameQualityMenu.html");
|
||||||
this.load.html(gameShare, "resources/html/gameShare.html");
|
this.load.html(gameShare, "resources/html/gameShare.html");
|
||||||
this.load.html(gameReportKey, gameReportRessource);
|
this.load.html(gameReportKey, gameReportRessource);
|
||||||
this.load.html(warningContainerKey, warningContainerHtml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
@ -147,7 +143,6 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
this.menuElement.addListener("click");
|
this.menuElement.addListener("click");
|
||||||
this.menuElement.on("click", this.onMenuClick.bind(this));
|
this.menuElement.on("click", this.onMenuClick.bind(this));
|
||||||
|
|
||||||
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
|
|
||||||
chatVisibilityStore.subscribe((v) => {
|
chatVisibilityStore.subscribe((v) => {
|
||||||
this.menuButton.setVisible(!v);
|
this.menuButton.setVisible(!v);
|
||||||
});
|
});
|
||||||
@ -194,20 +189,6 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private showWorldCapacityWarning() {
|
|
||||||
if (!this.warningContainer) {
|
|
||||||
this.warningContainer = new WarningContainer(this);
|
|
||||||
}
|
|
||||||
if (this.warningContainerTimeout) {
|
|
||||||
clearTimeout(this.warningContainerTimeout);
|
|
||||||
}
|
|
||||||
this.warningContainerTimeout = setTimeout(() => {
|
|
||||||
this.warningContainer?.destroy();
|
|
||||||
this.warningContainer = null;
|
|
||||||
this.warningContainerTimeout = null;
|
|
||||||
}, 120000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeSideMenu(): void {
|
private closeSideMenu(): void {
|
||||||
if (!this.sideMenuOpened) return;
|
if (!this.sideMenuOpened) return;
|
||||||
this.sideMenuOpened = false;
|
this.sideMenuOpened = false;
|
||||||
@ -363,6 +344,9 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
case "editGameSettingsButton":
|
case "editGameSettingsButton":
|
||||||
this.openGameSettingsMenu();
|
this.openGameSettingsMenu();
|
||||||
break;
|
break;
|
||||||
|
case "oidcLogin":
|
||||||
|
connectionManager.loadOpenIDScreen();
|
||||||
|
break;
|
||||||
case "toggleFullscreen":
|
case "toggleFullscreen":
|
||||||
this.toggleFullscreen();
|
this.toggleFullscreen();
|
||||||
break;
|
break;
|
||||||
@ -403,6 +387,10 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
private gotToCreateMapPage() {
|
private gotToCreateMapPage() {
|
||||||
//const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
|
//const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
|
||||||
//TODO fix me: this button can to send us on WorkAdventure BO.
|
//TODO fix me: this button can to send us on WorkAdventure BO.
|
||||||
|
//const sparkHost = ADMIN_URL + "/getting-started";
|
||||||
|
|
||||||
|
//The redirection must be only on workadventu.re domain
|
||||||
|
//To day the domain staging cannot be use by customer
|
||||||
const sparkHost = "https://workadventu.re/getting-started";
|
const sparkHost = "https://workadventu.re/getting-started";
|
||||||
window.open(sparkHost, "_blank");
|
window.open(sparkHost, "_blank");
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,6 @@ import { writable } from "svelte/store";
|
|||||||
|
|
||||||
export const userMovingStore = writable(false);
|
export const userMovingStore = writable(false);
|
||||||
|
|
||||||
export const requestVisitCardsStore = writable<string|null>(null);
|
export const requestVisitCardsStore = writable<string | null>(null);
|
||||||
|
|
||||||
|
export const userIsAdminStore = writable(false);
|
||||||
|
@ -1,3 +1,23 @@
|
|||||||
import { derived, writable, Writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
import Timeout = NodeJS.Timeout;
|
||||||
|
|
||||||
export const menuIconVisible = writable(false);
|
export const menuIconVisible = writable(false);
|
||||||
|
|
||||||
|
let warningContainerTimeout: Timeout | null = null;
|
||||||
|
function createWarningContainerStore() {
|
||||||
|
const { subscribe, set } = writable<boolean>(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
activateWarningContainer() {
|
||||||
|
set(true);
|
||||||
|
if (warningContainerTimeout) clearTimeout(warningContainerTimeout);
|
||||||
|
warningContainerTimeout = setTimeout(() => {
|
||||||
|
set(false);
|
||||||
|
warningContainerTimeout = null;
|
||||||
|
}, 120000);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const warningContainerStore = createWarningContainerStore();
|
||||||
|
@ -1,45 +1,46 @@
|
|||||||
import type {Room} from "../Connexion/Room";
|
import type { Room } from "../Connexion/Room";
|
||||||
|
|
||||||
export enum GameConnexionTypes {
|
export enum GameConnexionTypes {
|
||||||
anonymous=1,
|
anonymous = 1,
|
||||||
organization,
|
organization,
|
||||||
register,
|
register,
|
||||||
empty,
|
empty,
|
||||||
unknown,
|
unknown,
|
||||||
|
jwt,
|
||||||
}
|
}
|
||||||
|
|
||||||
//this class is responsible with analysing and editing the game's url
|
//this class is responsible with analysing and editing the game's url
|
||||||
class UrlManager {
|
class UrlManager {
|
||||||
|
|
||||||
//todo: use that to detect if we can find a token in localstorage
|
|
||||||
public getGameConnexionType(): GameConnexionTypes {
|
public getGameConnexionType(): GameConnexionTypes {
|
||||||
const url = window.location.pathname.toString();
|
const url = window.location.pathname.toString();
|
||||||
if (url.includes('_/')) {
|
if (url === "/jwt") {
|
||||||
|
return GameConnexionTypes.jwt;
|
||||||
|
} else if (url.includes("_/")) {
|
||||||
return GameConnexionTypes.anonymous;
|
return GameConnexionTypes.anonymous;
|
||||||
} else if (url.includes('@/')) {
|
} else if (url.includes("@/")) {
|
||||||
return GameConnexionTypes.organization;
|
return GameConnexionTypes.organization;
|
||||||
} else if(url.includes('register/')) {
|
} else if (url.includes("register/")) {
|
||||||
return GameConnexionTypes.register;
|
return GameConnexionTypes.register;
|
||||||
} else if(url === '/') {
|
} else if (url === "/") {
|
||||||
return GameConnexionTypes.empty;
|
return GameConnexionTypes.empty;
|
||||||
} else {
|
} else {
|
||||||
return GameConnexionTypes.unknown;
|
return GameConnexionTypes.unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOrganizationToken(): string|null {
|
public getOrganizationToken(): string | null {
|
||||||
const match = /\/register\/(.+)/.exec(window.location.pathname.toString());
|
const match = /\/register\/(.+)/.exec(window.location.pathname.toString());
|
||||||
return match ? match [1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public pushRoomIdToUrl(room:Room): void {
|
public pushRoomIdToUrl(room: Room): void {
|
||||||
if (window.location.pathname === room.id) return;
|
if (window.location.pathname === room.id) return;
|
||||||
const hash = window.location.hash;
|
const hash = window.location.hash;
|
||||||
const search = room.search.toString();
|
const search = room.search.toString();
|
||||||
history.pushState({}, 'WorkAdventure', room.id+(search?'?'+search:'')+hash);
|
history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStartLayerNameFromUrl(): string|null {
|
public getStartLayerNameFromUrl(): string | null {
|
||||||
const hash = window.location.hash;
|
const hash = window.location.hash;
|
||||||
return hash.length > 1 ? hash.substring(1) : null;
|
return hash.length > 1 ? hash.substring(1) : null;
|
||||||
}
|
}
|
||||||
|
@ -162,16 +162,3 @@ const app = new App({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
window.addEventListener("load", function () {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register("/resources/service-worker.js")
|
|
||||||
.then((serviceWorker) => {
|
|
||||||
console.log("Service Worker registered: ", serviceWorker);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error registering the Service Worker: ", error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
@import "style";
|
@import "style";
|
||||||
@import "mobile-style.scss";
|
@import "mobile-style.scss";
|
||||||
@import "fonts.scss";
|
@import "fonts.scss";
|
||||||
@import "svelte-style.scss";
|
@import "inputTextGlobalMessageSvelte-Style.scss";
|
||||||
|
24
front/style/inputTextGlobalMessageSvelte-Style.scss
Normal file
24
front/style/inputTextGlobalMessageSvelte-Style.scss
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//InputTextGlobalMessage
|
||||||
|
section.section-input-send-text {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.ql-toolbar{
|
||||||
|
max-height: 100px;
|
||||||
|
|
||||||
|
background: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.input-send-text{
|
||||||
|
height: auto;
|
||||||
|
max-height: calc(100% - 100px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
color: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
.ql-editor.ql-blank::before {
|
||||||
|
color: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
//Contains all styles not unique to a svelte component.
|
|
||||||
|
|
||||||
//ConsoleGlobalMessage
|
|
||||||
div.main-console.nes-container {
|
|
||||||
pointer-events: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
top: 20vh;
|
|
||||||
width: 50vw;
|
|
||||||
height: 50vh;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #333333;
|
|
||||||
|
|
||||||
.btn-action{
|
|
||||||
margin: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-global-message {
|
|
||||||
width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-global-message h2 {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.global-message {
|
|
||||||
display: flex;
|
|
||||||
max-height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.menu {
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.menu button {
|
|
||||||
margin: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-input {
|
|
||||||
width: 95%;
|
|
||||||
}
|
|
||||||
|
|
||||||
//InputTextGlobalMessage
|
|
||||||
.section-input-send-text {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-input-send-text .input-send-text .ql-editor{
|
|
||||||
color: white;
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-input-send-text .ql-toolbar{
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
@ -189,7 +189,7 @@ module.exports = {
|
|||||||
DISABLE_NOTIFICATIONS: false,
|
DISABLE_NOTIFICATIONS: false,
|
||||||
PUSHER_URL: undefined,
|
PUSHER_URL: undefined,
|
||||||
UPLOADER_URL: null,
|
UPLOADER_URL: null,
|
||||||
ADMIN_URL: null,
|
ADMIN_URL: undefined,
|
||||||
DEBUG_MODE: null,
|
DEBUG_MODE: null,
|
||||||
STUN_SERVER: null,
|
STUN_SERVER: null,
|
||||||
TURN_SERVER: null,
|
TURN_SERVER: null,
|
||||||
|
@ -291,6 +291,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
source-map "^0.6.1"
|
source-map "^0.6.1"
|
||||||
|
|
||||||
|
"@types/uuid@8.3.0":
|
||||||
|
version "8.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
|
||||||
|
integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
|
||||||
|
|
||||||
|
"@types/uuidv4@^5.0.0":
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuidv4/-/uuidv4-5.0.0.tgz#2c94e67b0c06d5adb28fb7ced1a1b5f0866ecd50"
|
||||||
|
integrity sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A==
|
||||||
|
dependencies:
|
||||||
|
uuidv4 "*"
|
||||||
|
|
||||||
"@types/webpack-dev-server@^3.11.4":
|
"@types/webpack-dev-server@^3.11.4":
|
||||||
version "3.11.4"
|
version "3.11.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.4.tgz#90d47dd660b696d409431ab8c1e9fa3615103a07"
|
resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.4.tgz#90d47dd660b696d409431ab8c1e9fa3615103a07"
|
||||||
@ -5775,11 +5787,24 @@ utils-merge@1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
uuid@8.3.2:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
uuid@^3.3.2, uuid@^3.4.0:
|
uuid@^3.3.2, uuid@^3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
|
uuidv4@*, uuidv4@^6.2.10:
|
||||||
|
version "6.2.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.10.tgz#42fc1c12b6f85ad536c2c5c1e836079d1e15003c"
|
||||||
|
integrity sha512-FMo1exd9l5UvoUPHRR6NrtJ/OJRePh0ca7IhPwBuMNuYRqjtuh8lE3WDxAUvZ4Yss5FbCOsPFjyWJf9lVTEmnw==
|
||||||
|
dependencies:
|
||||||
|
"@types/uuid" "8.3.0"
|
||||||
|
uuid "8.3.2"
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
|
v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||||
|
@ -58,11 +58,17 @@
|
|||||||
"height":94.6489098314831,
|
"height":94.6489098314831,
|
||||||
"id":1,
|
"id":1,
|
||||||
"name":"",
|
"name":"",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"font-family",
|
||||||
|
"type":"string",
|
||||||
|
"value":"\"Press Start 2P\""
|
||||||
|
}],
|
||||||
"rotation":0,
|
"rotation":0,
|
||||||
"text":
|
"text":
|
||||||
{
|
{
|
||||||
"fontfamily":"Sans Serif",
|
"fontfamily":"Sans Serif",
|
||||||
"pixelsize":11,
|
"pixelsize":8,
|
||||||
"text":"Test:\nWalk on the carpet and press space\nResult:\nJitsi opens on meet.jit.si (check this in the network tab). Note: this test only makes sense if the default configured Jitsi instance is NOT meet.jit.si (check your .env file)",
|
"text":"Test:\nWalk on the carpet and press space\nResult:\nJitsi opens on meet.jit.si (check this in the network tab). Note: this test only makes sense if the default configured Jitsi instance is NOT meet.jit.si (check your .env file)",
|
||||||
"wrap":true
|
"wrap":true
|
||||||
},
|
},
|
||||||
|
@ -247,6 +247,8 @@ message RefreshRoomMessage{
|
|||||||
|
|
||||||
message WorldFullMessage{
|
message WorldFullMessage{
|
||||||
}
|
}
|
||||||
|
message TokenExpiredMessage{
|
||||||
|
}
|
||||||
|
|
||||||
message WorldConnexionMessage{
|
message WorldConnexionMessage{
|
||||||
string message = 2;
|
string message = 2;
|
||||||
@ -278,6 +280,7 @@ message ServerToClientMessage {
|
|||||||
RefreshRoomMessage refreshRoomMessage = 17;
|
RefreshRoomMessage refreshRoomMessage = 17;
|
||||||
WorldConnexionMessage worldConnexionMessage = 18;
|
WorldConnexionMessage worldConnexionMessage = 18;
|
||||||
EmoteEventMessage emoteEventMessage = 19;
|
EmoteEventMessage emoteEventMessage = 19;
|
||||||
|
TokenExpiredMessage tokenExpiredMessage = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"grpc": "^1.24.4",
|
"grpc": "^1.24.4",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
|
"openid-client": "^4.7.4",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"query-string": "^6.13.3",
|
"query-string": "^6.13.3",
|
||||||
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
||||||
|
@ -4,6 +4,7 @@ import { BaseController } from "./BaseController";
|
|||||||
import { adminApi } from "../Services/AdminApi";
|
import { adminApi } from "../Services/AdminApi";
|
||||||
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
|
import { openIDClient } from "../Services/OpenIDClient";
|
||||||
|
|
||||||
export interface TokenInterface {
|
export interface TokenInterface {
|
||||||
userUuid: string;
|
userUuid: string;
|
||||||
@ -12,11 +13,58 @@ export interface TokenInterface {
|
|||||||
export class AuthenticateController extends BaseController {
|
export class AuthenticateController extends BaseController {
|
||||||
constructor(private App: TemplatedApp) {
|
constructor(private App: TemplatedApp) {
|
||||||
super();
|
super();
|
||||||
|
this.openIDLogin();
|
||||||
|
this.openIDCallback();
|
||||||
this.register();
|
this.register();
|
||||||
this.verify();
|
|
||||||
this.anonymLogin();
|
this.anonymLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openIDLogin() {
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.App.get("/login-screen", async (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn("/message request was aborted");
|
||||||
|
});
|
||||||
|
|
||||||
|
const { nonce, state } = parse(req.getQuery());
|
||||||
|
if (!state || !nonce) {
|
||||||
|
res.writeStatus("400 Unauthorized").end("missing state and nonce URL parameters");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const loginUri = await openIDClient.authorizationUrl(state as string, nonce as string);
|
||||||
|
res.writeStatus("302");
|
||||||
|
res.writeHeader("Location", loginUri);
|
||||||
|
return res.end();
|
||||||
|
} catch (e) {
|
||||||
|
return this.errorToResponse(e, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openIDCallback() {
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.App.get("/login-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn("/message request was aborted");
|
||||||
|
});
|
||||||
|
const { code, nonce } = parse(req.getQuery());
|
||||||
|
try {
|
||||||
|
const userInfo = await openIDClient.getUserInfo(code as string, nonce as string);
|
||||||
|
const email = userInfo.email || userInfo.sub;
|
||||||
|
if (!email) {
|
||||||
|
throw new Error("No email in the response");
|
||||||
|
}
|
||||||
|
const authToken = jwtTokenManager.createAuthToken(email);
|
||||||
|
res.writeStatus("200");
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
return res.end(JSON.stringify({ authToken }));
|
||||||
|
} catch (e) {
|
||||||
|
return this.errorToResponse(e, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//Try to login with an admin token
|
//Try to login with an admin token
|
||||||
private register() {
|
private register() {
|
||||||
this.App.options("/register", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/register", (res: HttpResponse, req: HttpRequest) => {
|
||||||
@ -39,11 +87,12 @@ export class AuthenticateController extends BaseController {
|
|||||||
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
||||||
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken);
|
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken);
|
||||||
const userUuid = data.userUuid;
|
const userUuid = data.userUuid;
|
||||||
|
const email = data.email;
|
||||||
const roomUrl = data.roomUrl;
|
const roomUrl = data.roomUrl;
|
||||||
const mapUrlStart = data.mapUrlStart;
|
const mapUrlStart = data.mapUrlStart;
|
||||||
const textures = data.textures;
|
const textures = data.textures;
|
||||||
|
|
||||||
const authToken = jwtTokenManager.createJWTToken(userUuid);
|
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(
|
res.end(
|
||||||
@ -63,45 +112,6 @@ export class AuthenticateController extends BaseController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private verify() {
|
|
||||||
this.App.options("/verify", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.App.get("/verify", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
(async () => {
|
|
||||||
const query = parse(req.getQuery());
|
|
||||||
|
|
||||||
res.onAborted(() => {
|
|
||||||
console.warn("verify request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await jwtTokenManager.getUserUuidFromToken(query.token as string);
|
|
||||||
} catch (e) {
|
|
||||||
res.writeStatus("400 Bad Request");
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end(
|
|
||||||
JSON.stringify({
|
|
||||||
success: false,
|
|
||||||
message: "Invalid JWT token",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.writeStatus("200 OK");
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end(
|
|
||||||
JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//permit to login on application. Return token to connect on Websocket IO.
|
//permit to login on application. Return token to connect on Websocket IO.
|
||||||
private anonymLogin() {
|
private anonymLogin() {
|
||||||
this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
||||||
@ -115,7 +125,7 @@ export class AuthenticateController extends BaseController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userUuid = v4();
|
const userUuid = v4();
|
||||||
const authToken = jwtTokenManager.createJWTToken(userUuid);
|
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(
|
res.end(
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||||
import { TemplatedApp } from "uWebSockets.js";
|
import { TemplatedApp } from "uWebSockets.js";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { SocketManager, socketManager } from "../Services/SocketManager";
|
import { SocketManager, socketManager } from "../Services/SocketManager";
|
||||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||||
@ -173,31 +173,34 @@ export class IoSocketController {
|
|||||||
characterLayers = [characterLayers];
|
characterLayers = [characterLayers];
|
||||||
}
|
}
|
||||||
|
|
||||||
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
|
const tokenData =
|
||||||
|
token && typeof token === "string" ? jwtTokenManager.decodeJWTToken(token) : null;
|
||||||
|
const userIdentifier = tokenData ? tokenData.identifier : "";
|
||||||
|
|
||||||
let memberTags: string[] = [];
|
let memberTags: string[] = [];
|
||||||
let memberVisitCardUrl: string | null = null;
|
let memberVisitCardUrl: string | null = null;
|
||||||
let memberMessages: unknown;
|
let memberMessages: unknown;
|
||||||
let memberTextures: CharacterTexture[] = [];
|
let memberTextures: CharacterTexture[] = [];
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
if (ADMIN_API_URL) {
|
|
||||||
try {
|
|
||||||
let userData: FetchMemberDataByUuidResponse = {
|
let userData: FetchMemberDataByUuidResponse = {
|
||||||
uuid: v4(),
|
userUuid: userIdentifier,
|
||||||
tags: [],
|
tags: [],
|
||||||
visitCardUrl: null,
|
visitCardUrl: null,
|
||||||
textures: [],
|
textures: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
anonymous: true,
|
anonymous: true,
|
||||||
};
|
};
|
||||||
|
if (ADMIN_API_URL) {
|
||||||
try {
|
try {
|
||||||
userData = await adminApi.fetchMemberDataByUuid(userUuid, roomId);
|
try {
|
||||||
|
userData = await adminApi.fetchMemberDataByUuid(userIdentifier, roomId, IPAddress);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err?.response?.status == 404) {
|
if (err?.response?.status == 404) {
|
||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
||||||
|
|
||||||
console.warn(
|
console.warn(
|
||||||
'Cannot find user with uuid "' +
|
'Cannot find user with email "' +
|
||||||
userUuid +
|
(userIdentifier || "anonymous") +
|
||||||
'". Performing an anonymous login instead.'
|
'". Performing an anonymous login instead.'
|
||||||
);
|
);
|
||||||
} else if (err?.response?.status == 403) {
|
} else if (err?.response?.status == 403) {
|
||||||
@ -235,7 +238,12 @@ export class IoSocketController {
|
|||||||
throw new Error("Use the login URL to connect");
|
throw new Error("Use the login URL to connect");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("access not granted for user " + userUuid + " and room " + roomId);
|
console.log(
|
||||||
|
"access not granted for user " +
|
||||||
|
(userIdentifier || "anonymous") +
|
||||||
|
" and room " +
|
||||||
|
roomId
|
||||||
|
);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new Error("User cannot access this world");
|
throw new Error("User cannot access this world");
|
||||||
}
|
}
|
||||||
@ -257,7 +265,7 @@ export class IoSocketController {
|
|||||||
// Data passed here is accessible on the "websocket" socket object.
|
// Data passed here is accessible on the "websocket" socket object.
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
userUuid,
|
userUuid: userData.userUuid,
|
||||||
IPAddress,
|
IPAddress,
|
||||||
roomId,
|
roomId,
|
||||||
name,
|
name,
|
||||||
@ -287,15 +295,10 @@ export class IoSocketController {
|
|||||||
context
|
context
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/*if (e instanceof Error) {
|
res.upgrade(
|
||||||
console.log(e.message);
|
|
||||||
res.writeStatus("401 Unauthorized").end(e.message);
|
|
||||||
} else {
|
|
||||||
res.writeStatus("500 Internal Server Error").end('An error occurred');
|
|
||||||
}*/
|
|
||||||
return res.upgrade(
|
|
||||||
{
|
{
|
||||||
rejected: true,
|
rejected: true,
|
||||||
|
reason: e.reason || null,
|
||||||
message: e.message ? e.message : "500 Internal Server Error",
|
message: e.message ? e.message : "500 Internal Server Error",
|
||||||
},
|
},
|
||||||
websocketKey,
|
websocketKey,
|
||||||
@ -310,12 +313,14 @@ export class IoSocketController {
|
|||||||
open: (ws) => {
|
open: (ws) => {
|
||||||
if (ws.rejected === true) {
|
if (ws.rejected === true) {
|
||||||
//FIX ME to use status code
|
//FIX ME to use status code
|
||||||
if (ws.message === "World is full") {
|
if (ws.reason === tokenInvalidException) {
|
||||||
|
socketManager.emitTokenExpiredMessage(ws);
|
||||||
|
} else if (ws.message === "World is full") {
|
||||||
socketManager.emitWorldFullMessage(ws);
|
socketManager.emitWorldFullMessage(ws);
|
||||||
} else {
|
} else {
|
||||||
socketManager.emitConnexionErrorMessage(ws, ws.message as string);
|
socketManager.emitConnexionErrorMessage(ws, ws.message as string);
|
||||||
}
|
}
|
||||||
ws.close();
|
setTimeout(() => ws.close(), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
||||||
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
|
|
||||||
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
|
||||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||||
const API_URL = process.env.API_URL || "";
|
const API_URL = process.env.API_URL || "";
|
||||||
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,14 +10,16 @@ const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || "";
|
|||||||
const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || "8080") || 8080;
|
const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || "8080") || 8080;
|
||||||
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 FRONT_URL = process.env.FRONT_URL || "http://localhost";
|
||||||
|
export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || "";
|
||||||
|
export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
||||||
|
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SECRET_KEY,
|
SECRET_KEY,
|
||||||
MINIMUM_DISTANCE,
|
|
||||||
API_URL,
|
API_URL,
|
||||||
ADMIN_API_URL,
|
ADMIN_API_URL,
|
||||||
ADMIN_API_TOKEN,
|
ADMIN_API_TOKEN,
|
||||||
MAX_USERS_PER_ROOM,
|
|
||||||
GROUP_RADIUS,
|
|
||||||
ALLOW_ARTILLERY,
|
ALLOW_ARTILLERY,
|
||||||
CPU_OVERHEAT_THRESHOLD,
|
CPU_OVERHEAT_THRESHOLD,
|
||||||
JITSI_URL,
|
JITSI_URL,
|
||||||
|
@ -7,6 +7,7 @@ import { RoomRedirect } from "./AdminApi/RoomRedirect";
|
|||||||
|
|
||||||
export interface AdminApiData {
|
export interface AdminApiData {
|
||||||
roomUrl: string;
|
roomUrl: string;
|
||||||
|
email: string | null;
|
||||||
mapUrlStart: string;
|
mapUrlStart: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
policy_type: number;
|
policy_type: number;
|
||||||
@ -21,7 +22,7 @@ export interface AdminBannedData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FetchMemberDataByUuidResponse {
|
export interface FetchMemberDataByUuidResponse {
|
||||||
uuid: string;
|
userUuid: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
visitCardUrl: string | null;
|
visitCardUrl: string | null;
|
||||||
textures: CharacterTexture[];
|
textures: CharacterTexture[];
|
||||||
@ -46,12 +47,16 @@ class AdminApi {
|
|||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
|
async fetchMemberDataByUuid(
|
||||||
|
userIdentifier: string | null,
|
||||||
|
roomId: string,
|
||||||
|
ipAddress: string
|
||||||
|
): Promise<FetchMemberDataByUuidResponse> {
|
||||||
if (!ADMIN_API_URL) {
|
if (!ADMIN_API_URL) {
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
}
|
}
|
||||||
const res = await Axios.get(ADMIN_API_URL + "/api/room/access", {
|
const res = await Axios.get(ADMIN_API_URL + "/api/room/access", {
|
||||||
params: { uuid, roomId },
|
params: { userIdentifier, roomId, ipAddress },
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
});
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
|
@ -1,100 +1,25 @@
|
|||||||
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import { uuid } from "uuidv4";
|
import { uuid } from "uuidv4";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt, { verify } from "jsonwebtoken";
|
||||||
import { TokenInterface } from "../Controller/AuthenticateController";
|
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||||
import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||||
|
|
||||||
|
export interface AuthTokenData {
|
||||||
|
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||||
|
}
|
||||||
|
export const tokenInvalidException = "tokenInvalid";
|
||||||
|
|
||||||
class JWTTokenManager {
|
class JWTTokenManager {
|
||||||
public createJWTToken(userUuid: string) {
|
public createAuthToken(identifier: string) {
|
||||||
return Jwt.sign({ userUuid: userUuid }, SECRET_KEY, { expiresIn: "200d" }); //todo: add a mechanic to refresh or recreate token
|
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "3d" });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserUuidFromToken(token: unknown, ipAddress?: string, roomUrl?: string): Promise<string> {
|
public decodeJWTToken(token: string): AuthTokenData {
|
||||||
if (!token) {
|
try {
|
||||||
throw new Error("An authentication error happened, a user tried to connect without a token.");
|
return Jwt.verify(token, SECRET_KEY, { ignoreExpiration: false }) as AuthTokenData;
|
||||||
|
} catch (e) {
|
||||||
|
throw { reason: tokenInvalidException, message: e.message };
|
||||||
}
|
}
|
||||||
if (typeof token !== "string") {
|
|
||||||
throw new Error("Token is expected to be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token === "test") {
|
|
||||||
if (ALLOW_ARTILLERY) {
|
|
||||||
return uuid();
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
"In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
Jwt.verify(token, SECRET_KEY, {}, (err, tokenDecoded) => {
|
|
||||||
const tokenInterface = tokenDecoded as TokenInterface;
|
|
||||||
if (err) {
|
|
||||||
console.error("An authentication error happened, invalid JsonWebToken.", err);
|
|
||||||
reject(new Error("An authentication error happened, invalid JsonWebToken. " + err.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tokenDecoded === undefined) {
|
|
||||||
console.error("Empty token found.");
|
|
||||||
reject(new Error("Empty token found."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//verify token
|
|
||||||
if (!this.isValidToken(tokenInterface)) {
|
|
||||||
reject(new Error("Authentication error, invalid token structure."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ADMIN_API_URL) {
|
|
||||||
//verify user in admin
|
|
||||||
let promise = new Promise((resolve) => resolve());
|
|
||||||
if (ipAddress && roomUrl) {
|
|
||||||
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, roomUrl);
|
|
||||||
}
|
|
||||||
promise
|
|
||||||
.then(() => {
|
|
||||||
adminApi
|
|
||||||
.fetchCheckUserByToken(tokenInterface.userUuid)
|
|
||||||
.then(() => {
|
|
||||||
resolve(tokenInterface.userUuid);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
//anonymous user
|
|
||||||
if (err.response && err.response.status && err.response.status === 404) {
|
|
||||||
resolve(tokenInterface.userUuid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve(tokenInterface.userUuid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
|
|
||||||
return adminApi
|
|
||||||
.verifyBanUser(userUuid, ipAddress, roomUrl)
|
|
||||||
.then((data: AdminBannedData) => {
|
|
||||||
if (data && data.is_banned) {
|
|
||||||
throw new Error("User was banned");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private isValidToken(token: object): token is TokenInterface {
|
|
||||||
return !(typeof (token as TokenInterface).userUuid !== "string");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
pusher/src/Services/OpenIDClient.ts
Normal file
43
pusher/src/Services/OpenIDClient.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Issuer, Client } from "openid-client";
|
||||||
|
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||||
|
|
||||||
|
class OpenIDClient {
|
||||||
|
private issuerPromise: Promise<Client> | null = null;
|
||||||
|
|
||||||
|
private initClient(): Promise<Client> {
|
||||||
|
if (!this.issuerPromise) {
|
||||||
|
this.issuerPromise = Issuer.discover(OPID_CLIENT_ISSUER).then((issuer) => {
|
||||||
|
return new issuer.Client({
|
||||||
|
client_id: OPID_CLIENT_ID,
|
||||||
|
client_secret: OPID_CLIENT_SECRET,
|
||||||
|
redirect_uris: [opidRedirectUri],
|
||||||
|
response_types: ["code"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.issuerPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public authorizationUrl(state: string, nonce: string) {
|
||||||
|
return this.initClient().then((client) => {
|
||||||
|
return client.authorizationUrl({
|
||||||
|
scope: "openid email",
|
||||||
|
prompt: "login",
|
||||||
|
state: state,
|
||||||
|
nonce: nonce,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string }> {
|
||||||
|
return this.initClient().then((client) => {
|
||||||
|
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||||
|
return client.userinfo(tokenSet);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const openIDClient = new OpenIDClient();
|
@ -28,6 +28,7 @@ import {
|
|||||||
UserLeftRoomMessage,
|
UserLeftRoomMessage,
|
||||||
AdminMessage,
|
AdminMessage,
|
||||||
BanMessage,
|
BanMessage,
|
||||||
|
TokenExpiredMessage,
|
||||||
RefreshRoomMessage,
|
RefreshRoomMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
@ -117,7 +118,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
console.warn("Admin connection lost to back server");
|
console.warn("Admin connection lost to back server");
|
||||||
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
|
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
|
||||||
if (!client.disconnecting) {
|
if (!client.disconnecting) {
|
||||||
this.closeWebsocketConnection(client, 1011, "Connection lost to back server");
|
this.closeWebsocketConnection(client, 1011, "Admin Connection lost to back server");
|
||||||
}
|
}
|
||||||
console.log("A user left");
|
console.log("A user left");
|
||||||
})
|
})
|
||||||
@ -140,24 +141,6 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdminSocketDataFor(roomId: string): AdminSocketData {
|
|
||||||
throw new Error("Not reimplemented yet");
|
|
||||||
/*const data:AdminSocketData = {
|
|
||||||
rooms: {},
|
|
||||||
users: {},
|
|
||||||
}
|
|
||||||
const room = this.Worlds.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;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
|
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
|
||||||
const viewport = client.viewport;
|
const viewport = client.viewport;
|
||||||
try {
|
try {
|
||||||
@ -598,8 +581,20 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setWorldfullmessage(errorMessage);
|
serverToClientMessage.setWorldfullmessage(errorMessage);
|
||||||
|
|
||||||
|
if (!client.disconnecting) {
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
public emitTokenExpiredMessage(client: WebSocket) {
|
||||||
|
const errorMessage = new TokenExpiredMessage();
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setTokenexpiredmessage(errorMessage);
|
||||||
|
|
||||||
|
if (!client.disconnecting) {
|
||||||
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public emitConnexionErrorMessage(client: WebSocket, message: string) {
|
public emitConnexionErrorMessage(client: WebSocket, message: string) {
|
||||||
const errorMessage = new WorldConnexionMessage();
|
const errorMessage = new WorldConnexionMessage();
|
||||||
|
1820
pusher/yarn.lock
1820
pusher/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user