Merge branch 'develop' of github.com:thecodingmachine/workadventure into trigger-message-refv3
This commit is contained in:
commit
4713010929
@ -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.
|
||||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,4 +1,13 @@
|
|||||||
## Version 1.4.x-dev
|
## Version develop
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- New scripting API features :
|
||||||
|
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
||||||
|
- Rewrote the way authentification works: the auth jwt token can now contains an email instead of an uuid
|
||||||
|
- Added an OpenId login flow than can be plugged to any OIDC provider.
|
||||||
|
- You can send a message to all rooms of your world from the console global message (user with tag admin only).
|
||||||
|
|
||||||
|
## Version 1.4.11
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
@ -28,7 +37,7 @@
|
|||||||
- Use `WA.state.[any variable]: unknown` to access directly any variable (this is a shortcut to using `WA.state.loadVariable` and `WA.state.saveVariable`)
|
- Use `WA.state.[any variable]: unknown` to access directly any variable (this is a shortcut to using `WA.state.loadVariable` and `WA.state.saveVariable`)
|
||||||
- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked.
|
- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked.
|
||||||
- The text chat was redesigned to be prettier and to use more features :
|
- The text chat was redesigned to be prettier and to use more features :
|
||||||
- The chat is now persistent bewteen discussions and always accesible
|
- The chat is now persistent between discussions and always accessible
|
||||||
- The chat now tracks incoming and outcoming users in your conversation
|
- The chat now tracks incoming and outcoming users in your conversation
|
||||||
- The chat allows your to see the visit card of users
|
- The chat allows your to see the visit card of users
|
||||||
- You can close the chat window with the escape key
|
- You can close the chat window with the escape key
|
||||||
|
@ -104,6 +104,15 @@ export class GameRoom {
|
|||||||
public getUserById(id: number): User | undefined {
|
public getUserById(id: number): User | undefined {
|
||||||
return this.users.get(id);
|
return this.users.get(id);
|
||||||
}
|
}
|
||||||
|
public getUsersByUuid(uuid: string): User[] {
|
||||||
|
const userList: User[] = [];
|
||||||
|
for (const user of this.users.values()) {
|
||||||
|
if (user.uuid === uuid) {
|
||||||
|
userList.push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userList;
|
||||||
|
}
|
||||||
|
|
||||||
public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
||||||
const positionMessage = joinRoomMessage.getPositionmessage();
|
const positionMessage = joinRoomMessage.getPositionmessage();
|
||||||
|
@ -57,7 +57,7 @@ const roomManager: IRoomManagerServer = {
|
|||||||
room = gameRoom;
|
room = gameRoom;
|
||||||
user = myUser;
|
user = myUser;
|
||||||
} else {
|
} else {
|
||||||
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
//Connection may have been closed before the init was finished, so we have to manually disconnect the user.
|
||||||
socketManager.leaveRoom(gameRoom, myUser);
|
socketManager.leaveRoom(gameRoom, myUser);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -272,7 +272,7 @@ const roomManager: IRoomManagerServer = {
|
|||||||
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
// FIXME: we could improve return message by returning a Success|ErrorMessage message
|
// FIXME: we could improve return message by returning a Success|ErrorMessage message
|
||||||
socketManager
|
socketManager
|
||||||
.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage())
|
.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage(), call.request.getType())
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
@ -701,8 +701,8 @@ export class SocketManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = room.getUserByUuid(recipientUuid);
|
const recipients = room.getUsersByUuid(recipientUuid);
|
||||||
if (recipient === undefined) {
|
if (recipients.length === 0) {
|
||||||
console.error(
|
console.error(
|
||||||
"In sendAdminMessage, could not find user with id '" +
|
"In sendAdminMessage, could not find user with id '" +
|
||||||
recipientUuid +
|
recipientUuid +
|
||||||
@ -711,6 +711,7 @@ export class SocketManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const recipient of recipients) {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType("ban"); //todo: is the type correct?
|
sendUserMessage.setType("ban"); //todo: is the type correct?
|
||||||
@ -720,6 +721,7 @@ export class SocketManager {
|
|||||||
|
|
||||||
recipient.socket.write(serverToClientMessage);
|
recipient.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async banUser(roomId: string, recipientUuid: string, message: string): Promise<void> {
|
public async banUser(roomId: string, recipientUuid: string, message: string): Promise<void> {
|
||||||
const room = await this.roomsPromises.get(roomId);
|
const room = await this.roomsPromises.get(roomId);
|
||||||
@ -732,8 +734,8 @@ export class SocketManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = room.getUserByUuid(recipientUuid);
|
const recipients = room.getUsersByUuid(recipientUuid);
|
||||||
if (recipient === undefined) {
|
if (recipients.length === 0) {
|
||||||
console.error(
|
console.error(
|
||||||
"In banUser, could not find user with id '" +
|
"In banUser, could not find user with id '" +
|
||||||
recipientUuid +
|
recipientUuid +
|
||||||
@ -742,6 +744,7 @@ export class SocketManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const recipient of recipients) {
|
||||||
// Let's leave the room now.
|
// Let's leave the room now.
|
||||||
room.leave(recipient);
|
room.leave(recipient);
|
||||||
|
|
||||||
@ -756,8 +759,9 @@ export class SocketManager {
|
|||||||
recipient.socket.write(serverToClientMessage);
|
recipient.socket.write(serverToClientMessage);
|
||||||
recipient.socket.end();
|
recipient.socket.end();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sendAdminRoomMessage(roomId: string, message: string) {
|
async sendAdminRoomMessage(roomId: string, message: string, type: string) {
|
||||||
const room = await this.roomsPromises.get(roomId);
|
const room = await this.roomsPromises.get(roomId);
|
||||||
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
|
||||||
@ -772,7 +776,7 @@ export class SocketManager {
|
|||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType("message");
|
sendUserMessage.setType(type);
|
||||||
|
|
||||||
const clientMessage = new ServerToClientMessage();
|
const clientMessage = new ServerToClientMessage();
|
||||||
clientMessage.setSendusermessage(sendUserMessage);
|
clientMessage.setSendusermessage(sendUserMessage);
|
||||||
@ -786,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,
|
||||||
@ -23,7 +22,7 @@ async function startOneUser(): Promise<void> {
|
|||||||
bottom: 200,
|
bottom: 200,
|
||||||
left: 500,
|
left: 500,
|
||||||
right: 800
|
right: 800
|
||||||
});
|
}, null);
|
||||||
|
|
||||||
const connection = onConnect.connection;
|
const connection = onConnect.connection;
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -163,3 +163,17 @@ WA.room.setTiles([
|
|||||||
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Loading a tileset
|
||||||
|
```
|
||||||
|
WA.room.loadTileset(url: string): Promise<number>
|
||||||
|
```
|
||||||
|
Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset.
|
||||||
|
|
||||||
|
You can create a tileset file in Tile Editor.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
||||||
|
WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]);
|
||||||
|
})
|
||||||
|
```
|
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>
|
@ -56,7 +56,7 @@ A few things to notice:
|
|||||||
|
|
||||||
## Building walls and "collidable" areas
|
## Building walls and "collidable" areas
|
||||||
|
|
||||||
By default, the characters can traverse any tiles. If you want to prevent your characeter from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile.
|
By default, the characters can traverse any tiles. If you want to prevent your character from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile.
|
||||||
|
|
||||||
To make a tile "collidable", you should:
|
To make a tile "collidable", you should:
|
||||||
|
|
||||||
|
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">
|
||||||
|
4
front/dist/resources/html/gameMenu.html
vendored
4
front/dist/resources/html/gameMenu.html
vendored
@ -60,6 +60,10 @@
|
|||||||
<section>
|
<section>
|
||||||
<button id="enableNotification">Enable notifications</button>
|
<button id="enableNotification">Enable notifications</button>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- TODO activate authentication -->
|
||||||
|
<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": [
|
||||||
|
8393
front/package-lock.json
generated
8393
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||||
@ -50,10 +51,12 @@
|
|||||||
"phaser3-rex-plugins": "^1.1.42",
|
"phaser3-rex-plugins": "^1.1.42",
|
||||||
"queue-typescript": "^1.0.1",
|
"queue-typescript": "^1.0.1",
|
||||||
"quill": "1.3.6",
|
"quill": "1.3.6",
|
||||||
|
"quill-delta-to-html": "^0.12.0",
|
||||||
"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",
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
|
||||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
|
||||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
|
||||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
|
||||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
|
||||||
import {soundManager} from "../Phaser/Game/SoundManager";
|
|
||||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
|
||||||
|
|
||||||
export class GlobalMessageManager {
|
|
||||||
|
|
||||||
constructor(private Connection: RoomConnection) {
|
|
||||||
this.initialise();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialise(){
|
|
||||||
//receive signal to show message
|
|
||||||
this.Connection.receivePlayGlobalMessage((message: PlayGlobalMessageInterface) => {
|
|
||||||
this.playMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
//receive signal to close message
|
|
||||||
this.Connection.receiveStopGlobalMessage((messageId: string) => {
|
|
||||||
this.stopMessage(messageId);
|
|
||||||
});
|
|
||||||
|
|
||||||
//receive signal to close message
|
|
||||||
this.Connection.receiveTeleportMessage((map: string) => {
|
|
||||||
console.log('map to teleport user', map);
|
|
||||||
//TODO teleport user on map
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private playMessage(message : PlayGlobalMessageInterface){
|
|
||||||
const previousMessage = document.getElementById(this.getHtmlMessageId(message.id));
|
|
||||||
if(previousMessage){
|
|
||||||
previousMessage.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(AdminMessageEventTypes.audio === message.type){
|
|
||||||
this.playAudioMessage(message.id, message.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(AdminMessageEventTypes.admin === message.type){
|
|
||||||
this.playTextMessage(message.id, message.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private playAudioMessage(messageId : string, urlMessage: string) {
|
|
||||||
soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private playTextMessage(messageId : string, htmlMessage: string){
|
|
||||||
//add button to clear message
|
|
||||||
const buttonText = document.createElement('p');
|
|
||||||
buttonText.id = 'button-clear-message';
|
|
||||||
buttonText.innerText = 'Clear';
|
|
||||||
|
|
||||||
const buttonMainConsole = document.createElement('div');
|
|
||||||
buttonMainConsole.classList.add('clear');
|
|
||||||
buttonMainConsole.appendChild(buttonText);
|
|
||||||
buttonMainConsole.addEventListener('click', () => {
|
|
||||||
messageContainer.style.top = '-80%';
|
|
||||||
setTimeout(() => {
|
|
||||||
messageContainer.remove();
|
|
||||||
buttonMainConsole.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//create content message
|
|
||||||
const messageCotent = document.createElement('div');
|
|
||||||
messageCotent.innerHTML = htmlMessage;
|
|
||||||
messageCotent.className = "content-message";
|
|
||||||
|
|
||||||
//add message container
|
|
||||||
const messageContainer = document.createElement('div');
|
|
||||||
messageContainer.id = this.getHtmlMessageId(messageId);
|
|
||||||
messageContainer.className = "message-container";
|
|
||||||
messageContainer.appendChild(messageCotent);
|
|
||||||
messageContainer.appendChild(buttonMainConsole);
|
|
||||||
|
|
||||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
|
||||||
mainSectionDiv.appendChild(messageContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopMessage(messageId: string){
|
|
||||||
HtmlUtils.removeElementByIdOrFail<HTMLDivElement>(this.getHtmlMessageId(messageId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getHtmlMessageId(messageId: string) : string{
|
|
||||||
return `message-${messageId}`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
import type {TypeMessageInterface} from "./UserMessageManager";
|
|
||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
|
||||||
|
|
||||||
let modalTimeOut : NodeJS.Timeout;
|
|
||||||
|
|
||||||
export class TypeMessageExt implements TypeMessageInterface{
|
|
||||||
private nbSecond = 0;
|
|
||||||
private maxNbSecond = 10;
|
|
||||||
private titleMessage = 'IMPORTANT !';
|
|
||||||
|
|
||||||
showMessage(message: string, canDeleteMessage: boolean = true): void {
|
|
||||||
//delete previous modal
|
|
||||||
try{
|
|
||||||
if(modalTimeOut){
|
|
||||||
clearTimeout(modalTimeOut);
|
|
||||||
}
|
|
||||||
const modal = HtmlUtils.getElementByIdOrFail('report-message-user');
|
|
||||||
modal.remove();
|
|
||||||
}catch (err){
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
//create new modal
|
|
||||||
const div : HTMLDivElement = document.createElement('div');
|
|
||||||
div.classList.add('modal-report-user');
|
|
||||||
div.id = 'report-message-user';
|
|
||||||
div.style.backgroundColor = '#000000e0';
|
|
||||||
|
|
||||||
const img : HTMLImageElement = document.createElement('img');
|
|
||||||
img.src = 'resources/logos/report.svg';
|
|
||||||
div.appendChild(img);
|
|
||||||
|
|
||||||
const title : HTMLParagraphElement = document.createElement('p');
|
|
||||||
title.id = 'title-report-user';
|
|
||||||
title.innerText = `${this.titleMessage} (${this.maxNbSecond})`;
|
|
||||||
div.appendChild(title);
|
|
||||||
|
|
||||||
const p : HTMLParagraphElement = document.createElement('p');
|
|
||||||
p.id = 'body-report-user'
|
|
||||||
p.innerText = message;
|
|
||||||
div.appendChild(p);
|
|
||||||
|
|
||||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
|
||||||
mainSectionDiv.appendChild(div);
|
|
||||||
|
|
||||||
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
|
|
||||||
// FIXME: this will fail on iOS
|
|
||||||
// We should move the sound playing into the GameScene and listen to the event of a report using a store
|
|
||||||
try {
|
|
||||||
reportMessageAudio.play();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nbSecond = this.maxNbSecond;
|
|
||||||
setTimeout((c) => {
|
|
||||||
this.forMessage(title, canDeleteMessage);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
forMessage(title: HTMLParagraphElement, canDeleteMessage: boolean = true){
|
|
||||||
this.nbSecond -= 1;
|
|
||||||
title.innerText = `${this.titleMessage} (${this.nbSecond})`;
|
|
||||||
if(this.nbSecond > 0){
|
|
||||||
modalTimeOut = setTimeout(() => {
|
|
||||||
this.forMessage(title, canDeleteMessage);
|
|
||||||
}, 1000);
|
|
||||||
}else {
|
|
||||||
title.innerText = this.titleMessage;
|
|
||||||
|
|
||||||
if (!canDeleteMessage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const imgCancel: HTMLImageElement = document.createElement('img');
|
|
||||||
imgCancel.id = 'cancel-report-user';
|
|
||||||
imgCancel.src = 'resources/logos/close.svg';
|
|
||||||
|
|
||||||
const div = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('report-message-user');
|
|
||||||
div.appendChild(imgCancel);
|
|
||||||
imgCancel.addEventListener('click', () => {
|
|
||||||
div.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Message extends TypeMessageExt {}
|
|
||||||
|
|
||||||
export class Ban extends TypeMessageExt {}
|
|
||||||
|
|
||||||
export class Banned extends TypeMessageExt {
|
|
||||||
showMessage(message: string){
|
|
||||||
super.showMessage(message, false);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +1,34 @@
|
|||||||
import * as TypeMessages from "./TypeMessage";
|
import { AdminMessageEventTypes, adminMessagesService } from "../Connexion/AdminMessagesService";
|
||||||
import {Banned} from "./TypeMessage";
|
import { textMessageContentStore, textMessageVisibleStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
import {adminMessagesService} from "../Connexion/AdminMessagesService";
|
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||||
|
import { UPLOADER_URL } from "../Enum/EnvironmentVariable";
|
||||||
export interface TypeMessageInterface {
|
import { banMessageContentStore, banMessageVisibleStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
showMessage(message: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserMessageManager {
|
class UserMessageManager {
|
||||||
|
|
||||||
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
|
||||||
receiveBannedMessageListener!: Function;
|
receiveBannedMessageListener!: Function;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const valueTypeMessageTab = Object.values(TypeMessages);
|
|
||||||
Object.keys(TypeMessages).forEach((value: string, index: number) => {
|
|
||||||
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
|
|
||||||
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
|
|
||||||
});
|
|
||||||
|
|
||||||
adminMessagesService.messageStream.subscribe((event) => {
|
adminMessagesService.messageStream.subscribe((event) => {
|
||||||
const typeMessage = this.showMessage(event.type, event.text);
|
textMessageVisibleStore.set(false);
|
||||||
if(typeMessage instanceof Banned) {
|
banMessageVisibleStore.set(false);
|
||||||
|
if (event.type === AdminMessageEventTypes.admin) {
|
||||||
|
textMessageContentStore.set(event.text);
|
||||||
|
textMessageVisibleStore.set(true);
|
||||||
|
} else if (event.type === AdminMessageEventTypes.audio) {
|
||||||
|
soundPlayingStore.playSound(UPLOADER_URL + event.text);
|
||||||
|
} else if (event.type === AdminMessageEventTypes.ban) {
|
||||||
|
banMessageContentStore.set(event.text);
|
||||||
|
banMessageVisibleStore.set(true);
|
||||||
|
} else if (event.type === AdminMessageEventTypes.banned) {
|
||||||
|
banMessageContentStore.set(event.text);
|
||||||
|
banMessageVisibleStore.set(true);
|
||||||
this.receiveBannedMessageListener();
|
this.receiveBannedMessageListener();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
showMessage(type: string, message: string) {
|
|
||||||
const classTypeMessage = this.typeMessages.get(type.toLowerCase());
|
|
||||||
if (!classTypeMessage) {
|
|
||||||
console.error('Message unknown');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
classTypeMessage.showMessage(message);
|
|
||||||
return classTypeMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setReceiveBanListener(callback: Function) {
|
setReceiveBanListener(callback: Function) {
|
||||||
this.receiveBannedMessageListener = callback;
|
this.receiveBannedMessageListener = callback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const userMessageManager = new UserMessageManager()
|
export const userMessageManager = new UserMessageManager();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
import type { GameStateEvent } from "./GameStateEvent";
|
|
||||||
import type { ButtonClickedEvent } from "./ButtonClickedEvent";
|
import type { ButtonClickedEvent } from "./ButtonClickedEvent";
|
||||||
import type { ChatEvent } from "./ChatEvent";
|
import type { ChatEvent } from "./ChatEvent";
|
||||||
import type { ClosePopupEvent } from "./ClosePopupEvent";
|
import type { ClosePopupEvent } from "./ClosePopupEvent";
|
||||||
@ -19,16 +18,18 @@ import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
|||||||
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
||||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||||
import type { SetTilesEvent } from "./SetTilesEvent";
|
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||||
|
import type { SetVariableEvent } from "./SetVariableEvent";
|
||||||
|
import { isGameStateEvent } from "./GameStateEvent";
|
||||||
|
import { isMapDataEvent } from "./MapDataEvent";
|
||||||
|
import { isSetVariableEvent } from "./SetVariableEvent";
|
||||||
|
import type { LoadTilesetEvent } from "./LoadTilesetEvent";
|
||||||
|
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
|
||||||
import type {
|
import type {
|
||||||
MessageReferenceEvent,
|
MessageReferenceEvent,
|
||||||
removeTriggerMessage,
|
removeTriggerMessage,
|
||||||
triggerMessage,
|
triggerMessage,
|
||||||
TriggerMessageEvent,
|
TriggerMessageEvent,
|
||||||
} from "./ui/TriggerMessageEvent";
|
} from "./ui/TriggerMessageEvent";
|
||||||
import type { SetVariableEvent } from "./SetVariableEvent";
|
|
||||||
import { isGameStateEvent } from "./GameStateEvent";
|
|
||||||
import { isMapDataEvent } from "./MapDataEvent";
|
|
||||||
import { isSetVariableEvent } from "./SetVariableEvent";
|
|
||||||
import { isMessageReferenceEvent, isTriggerMessageEvent } from "./ui/TriggerMessageEvent";
|
import { isMessageReferenceEvent, isTriggerMessageEvent } from "./ui/TriggerMessageEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
@ -59,6 +60,7 @@ export type IframeEventMap = {
|
|||||||
playSound: PlaySoundEvent;
|
playSound: PlaySoundEvent;
|
||||||
stopSound: null;
|
stopSound: null;
|
||||||
getState: undefined;
|
getState: undefined;
|
||||||
|
loadTileset: LoadTilesetEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent;
|
registerMenuCommand: MenuItemRegisterEvent;
|
||||||
setTiles: SetTilesEvent;
|
setTiles: SetTilesEvent;
|
||||||
|
|
||||||
@ -111,6 +113,10 @@ export const iframeQueryMapTypeGuards = {
|
|||||||
query: isSetVariableEvent,
|
query: isSetVariableEvent,
|
||||||
answer: tg.isUndefined,
|
answer: tg.isUndefined,
|
||||||
},
|
},
|
||||||
|
loadTileset: {
|
||||||
|
query: isLoadTilesetEvent,
|
||||||
|
answer: tg.isNumber,
|
||||||
|
},
|
||||||
triggerMessage: {
|
triggerMessage: {
|
||||||
query: isTriggerMessageEvent,
|
query: isTriggerMessageEvent,
|
||||||
answer: tg.isUndefined,
|
answer: tg.isUndefined,
|
||||||
|
12
front/src/Api/Events/LoadTilesetEvent.ts
Normal file
12
front/src/Api/Events/LoadTilesetEvent.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isLoadTilesetEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type LoadTilesetEvent = tg.GuardedType<typeof isLoadTilesetEvent>;
|
@ -27,9 +27,6 @@ import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
|||||||
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
||||||
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
|
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
|
||||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||||
import { isMenuItemRegisterEvent } from "./Events/ui/MenuItemRegisterEvent";
|
|
||||||
import type { MapDataEvent } from "./Events/MapDataEvent";
|
|
||||||
import type { GameStateEvent } from "./Events/GameStateEvent";
|
|
||||||
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||||
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
|
@ -105,6 +105,14 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
|||||||
}
|
}
|
||||||
return mapURL;
|
return mapURL;
|
||||||
}
|
}
|
||||||
|
async loadTileset(url: string): Promise<number> {
|
||||||
|
return await queryWorkadventure({
|
||||||
|
type: "loadTileset",
|
||||||
|
data: {
|
||||||
|
url: url,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventureRoomCommands();
|
export default new WorkadventureRoomCommands();
|
||||||
|
@ -27,6 +27,12 @@
|
|||||||
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 AdminMessage from "./TypeMessage/BanMessage.svelte";
|
||||||
|
import TextMessage from "./TypeMessage/TextMessage.svelte";
|
||||||
|
import {banMessageVisibleStore} from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import {textMessageVisibleStore} from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
import {warningContainerStore} from "../Stores/MenuStore";
|
||||||
|
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -58,6 +64,16 @@
|
|||||||
<EnableCameraScene game={game}></EnableCameraScene>
|
<EnableCameraScene game={game}></EnableCameraScene>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $banMessageVisibleStore}
|
||||||
|
<div>
|
||||||
|
<AdminMessage></AdminMessage>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $textMessageVisibleStore}
|
||||||
|
<div>
|
||||||
|
<TextMessage></TextMessage>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if $soundPlayingStore}
|
{#if $soundPlayingStore}
|
||||||
<div>
|
<div>
|
||||||
<AudioPlaying url={$soundPlayingStore} />
|
<AudioPlaying url={$soundPlayingStore} />
|
||||||
@ -91,4 +107,7 @@
|
|||||||
{#if $chatVisibilityStore}
|
{#if $chatVisibilityStore}
|
||||||
<Chat></Chat>
|
<Chat></Chat>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $warningContainerStore}
|
||||||
|
<WarningContainer></WarningContainer>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
import ChatMessageForm from './ChatMessageForm.svelte';
|
import ChatMessageForm from './ChatMessageForm.svelte';
|
||||||
import ChatElement from './ChatElement.svelte';
|
import ChatElement from './ChatElement.svelte';
|
||||||
import {afterUpdate, beforeUpdate} from "svelte";
|
import {afterUpdate, beforeUpdate} from "svelte";
|
||||||
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
|
|
||||||
let listDom: HTMLElement;
|
let listDom: HTMLElement;
|
||||||
|
let chatWindowElement: HTMLElement;
|
||||||
|
let handleFormBlur: { blur():void };
|
||||||
let autoscroll: boolean;
|
let autoscroll: boolean;
|
||||||
|
|
||||||
beforeUpdate(() => {
|
beforeUpdate(() => {
|
||||||
@ -16,6 +19,12 @@
|
|||||||
if (autoscroll) listDom.scrollTo(0, listDom.scrollHeight);
|
if (autoscroll) listDom.scrollTo(0, listDom.scrollHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onClick(event: MouseEvent) {
|
||||||
|
if (HtmlUtils.isClickedOutside(event, chatWindowElement)) {
|
||||||
|
handleFormBlur.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeChat() {
|
function closeChat() {
|
||||||
chatVisibilityStore.set(false);
|
chatVisibilityStore.set(false);
|
||||||
}
|
}
|
||||||
@ -26,10 +35,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown}/>
|
<svelte:window on:keydown={onKeyDown} on:click={onClick}/>
|
||||||
|
|
||||||
|
|
||||||
<aside class="chatWindow" transition:fly="{{ x: -1000, duration: 500 }}">
|
<aside class="chatWindow" transition:fly="{{ x: -1000, duration: 500 }}" bind:this={chatWindowElement}>
|
||||||
<p class="close-icon" on:click={closeChat}>×</p>
|
<p class="close-icon" on:click={closeChat}>×</p>
|
||||||
<section class="messagesList" bind:this={listDom}>
|
<section class="messagesList" bind:this={listDom}>
|
||||||
<ul>
|
<ul>
|
||||||
@ -40,7 +49,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section class="messageForm">
|
<section class="messageForm">
|
||||||
<ChatMessageForm></ChatMessageForm>
|
<ChatMessageForm bind:handleForm={handleFormBlur}></ChatMessageForm>
|
||||||
</section>
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@
|
|||||||
|
|
||||||
export let message: ChatMessage;
|
export let message: ChatMessage;
|
||||||
export let line: number;
|
export let line: number;
|
||||||
|
const chatStyleLink = "color: white; text-decoration: underline;";
|
||||||
|
|
||||||
$: author = message.author as PlayerInterface;
|
$: author = message.author as PlayerInterface;
|
||||||
$: targets = message.targets || [];
|
$: targets = message.targets || [];
|
||||||
$: texts = message.text || [];
|
$: texts = message.text || [];
|
||||||
|
|
||||||
function urlifyText(text: string): string {
|
function urlifyText(text: string): string {
|
||||||
return HtmlUtils.urlify(text)
|
return HtmlUtils.urlify(text, chatStyleLink);
|
||||||
}
|
}
|
||||||
function renderDate(date: Date) {
|
function renderDate(date: Date) {
|
||||||
return date.toLocaleTimeString(navigator.language, {
|
return date.toLocaleTimeString(navigator.language, {
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {chatMessagesStore, chatInputFocusStore} from "../../Stores/ChatStore";
|
import {chatMessagesStore, chatInputFocusStore} from "../../Stores/ChatStore";
|
||||||
|
|
||||||
|
export const handleForm = {
|
||||||
|
blur() {
|
||||||
|
inputElement.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let inputElement: HTMLElement;
|
||||||
let newMessageText = '';
|
let newMessageText = '';
|
||||||
|
|
||||||
function onFocus() {
|
function onFocus() {
|
||||||
@ -18,7 +24,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={saveMessage}>
|
<form on:submit|preventDefault={saveMessage}>
|
||||||
<input type="text" bind:value={newMessageText} placeholder="Enter your message..." on:focus={onFocus} on:blur={onBlur} >
|
<input type="text" bind:value={newMessageText} placeholder="Enter your message..." on:focus={onFocus} on:blur={onBlur} bind:this={inputElement}>
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<img src="/static/images/send.png" alt="Send" width="20">
|
<img src="/static/images/send.png" alt="Send" width="20">
|
||||||
</button>
|
</button>
|
||||||
|
@ -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(broadcast: boolean): void };
|
||||||
|
let handleSendAudio: { sendAudioMessage(broadcast: boolean): 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(broadcastToWorld);
|
||||||
|
}
|
||||||
|
if (uploadMusicActive) {
|
||||||
|
handleSendAudio.sendAudioMessage(broadcastToWorld);
|
||||||
|
}
|
||||||
|
}
|
||||||
</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 {onDestroy, 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 { 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";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
|
|
||||||
//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));
|
const 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(broadcastToWorld: boolean) {
|
||||||
|
if (gameScene == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = JSON.stringify(quill.getContents(0, quill.getLength()));
|
||||||
|
|
||||||
|
const textGlobalMessage: PlayGlobalMessageInterface = {
|
||||||
|
type: MESSAGE_TYPE,
|
||||||
|
content: text,
|
||||||
|
broadcastToWorld: broadcastToWorld
|
||||||
|
};
|
||||||
|
|
||||||
|
quill.deleteText(0, quill.getLength());
|
||||||
|
gameScene.connection?.emitGlobalMessage(textGlobalMessage);
|
||||||
|
disableConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Quill
|
//Quill
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
||||||
@ -48,49 +66,28 @@
|
|||||||
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
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
quill.on('selection-change', function (range, oldRange) {
|
|
||||||
if (range === null && oldRange !== null) {
|
|
||||||
consoleGlobalMessageManagerFocusStore.set(false);
|
|
||||||
} else if (range !== null && oldRange === null)
|
|
||||||
consoleGlobalMessageManagerFocusStore.set(true);
|
consoleGlobalMessageManagerFocusStore.set(true);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
onDestroy(() => {
|
||||||
|
consoleGlobalMessageManagerFocusStore.set(false);
|
||||||
|
})
|
||||||
|
|
||||||
function disableConsole() {
|
function disableConsole() {
|
||||||
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>
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,9 +4,8 @@
|
|||||||
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 uploadFile from "../images/music-file.svg";
|
import uploadFile from "../images/music-file.svg";
|
||||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
||||||
|
|
||||||
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(broadcast: boolean) {
|
||||||
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,15 +38,16 @@
|
|||||||
fd.append('file', selectedFile);
|
fd.append('file', selectedFile);
|
||||||
const res = await gameScene.connection?.uploadAudio(fd);
|
const res = await gameScene.connection?.uploadAudio(fd);
|
||||||
|
|
||||||
const GlobalMessage: PlayGlobalMessageInterface = {
|
const audioGlobalMessage: PlayGlobalMessageInterface = {
|
||||||
id: (res as { id: string }).id,
|
content: (res as { path: string }).path,
|
||||||
message: (res as { path: string }).path,
|
type: AUDIO_TYPE,
|
||||||
type: AUDIO_TYPE
|
broadcastToWorld: broadcast
|
||||||
}
|
}
|
||||||
inputAudio.value = '';
|
inputAudio.value = '';
|
||||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
gameScene.connection?.emitGlobalMessage(audioGlobalMessage);
|
||||||
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>
|
96
front/src/Components/TypeMessage/BanMessage.svelte
Normal file
96
front/src/Components/TypeMessage/BanMessage.svelte
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import {banMessageVisibleStore, banMessageContentStore} from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
|
const text = $banMessageContentStore;
|
||||||
|
const NAME_BUTTON = 'Ok';
|
||||||
|
let nbSeconds = 10;
|
||||||
|
let nameButton = '';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
timeToRead()
|
||||||
|
})
|
||||||
|
|
||||||
|
function timeToRead() {
|
||||||
|
nbSeconds -= 1;
|
||||||
|
nameButton = nbSeconds.toString();
|
||||||
|
if ( nbSeconds > 0 ) {
|
||||||
|
setTimeout( () => {
|
||||||
|
timeToRead();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
nameButton = NAME_BUTTON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBanMessage() {
|
||||||
|
banMessageVisibleStore.set(false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main-ban-message nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
|
||||||
|
<h2 class="title-ban-message"><img src="resources/logos/report.svg" alt="***"/> Important message <img src="resources/logos/report.svg" alt="***"/></h2>
|
||||||
|
<div class="content-ban-message">
|
||||||
|
<p>{text}</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-ban-message">
|
||||||
|
<button type="button" class="nes-btn {nameButton === NAME_BUTTON ? 'is-primary' : 'is-error'}" disabled="{!(nameButton === NAME_BUTTON)}" on:click|preventDefault={closeBanMessage}>{nameButton}</button>
|
||||||
|
</div>
|
||||||
|
<audio id="report-message" autoplay>
|
||||||
|
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.main-ban-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
top: 15vh;
|
||||||
|
|
||||||
|
height: 70vh;
|
||||||
|
width: 60vw;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
pointer-events: auto;
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
h2.title-ban-message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: 50px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content-ban-message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: calc(100% - 50px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
p {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer-ban-message {
|
||||||
|
height: 50px;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 88px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
59
front/src/Components/TypeMessage/TextMessage.svelte
Normal file
59
front/src/Components/TypeMessage/TextMessage.svelte
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import {textMessageContentStore, textMessageVisibleStore} from "../../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
|
||||||
|
|
||||||
|
const content = JSON.parse($textMessageContentStore);
|
||||||
|
const converter = new QuillDeltaToHtmlConverter(content.ops, {inlineStyles: true});
|
||||||
|
const NAME_BUTTON = 'Ok';
|
||||||
|
|
||||||
|
function closeTextMessage() {
|
||||||
|
textMessageVisibleStore.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e:KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeTextMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown}/>
|
||||||
|
|
||||||
|
<div class="main-text-message nes-container is-rounded" transition:fly="{{ x: -1000, duration: 500 }}">
|
||||||
|
<div class="content-text-message">
|
||||||
|
{@html converter.convert()}
|
||||||
|
</div>
|
||||||
|
<div class="footer-text-message">
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click|preventDefault={closeTextMessage}>{NAME_BUTTON}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.main-text-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
max-height: 25vh;
|
||||||
|
width: 80vw;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
pointer-events: auto;
|
||||||
|
background-color: #333333;
|
||||||
|
|
||||||
|
div.content-text-message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: calc(100% - 50px);
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer-text-message {
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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) => {
|
||||||
|
@ -110,9 +110,9 @@ export interface RoomJoinedMessageInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PlayGlobalMessageInterface {
|
export interface PlayGlobalMessageInterface {
|
||||||
id: string;
|
|
||||||
type: string;
|
type: string;
|
||||||
message: string;
|
content: string;
|
||||||
|
broadcastToWorld: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnConnectInterface {
|
export interface OnConnectInterface {
|
||||||
|
@ -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,23 +1,28 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,21 +30,21 @@ class LocalUserStore {
|
|||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,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;
|
||||||
|
|
||||||
@ -76,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(
|
||||||
@ -217,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;
|
||||||
@ -244,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 {
|
||||||
@ -594,7 +597,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) {
|
/* public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) {
|
||||||
return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => {
|
return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => {
|
||||||
callback({
|
callback({
|
||||||
id: message.getId(),
|
id: message.getId(),
|
||||||
@ -602,7 +605,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
message: message.getMessage(),
|
message: message.getMessage(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}*/
|
||||||
|
|
||||||
public receiveStopGlobalMessage(callback: (messageId: string) => void) {
|
public receiveStopGlobalMessage(callback: (messageId: string) => void) {
|
||||||
return this.onMessage(EventMessage.STOP_GLOBAL_MESSAGE, (message: StopGlobalMessage) => {
|
return this.onMessage(EventMessage.STOP_GLOBAL_MESSAGE, (message: StopGlobalMessage) => {
|
||||||
@ -616,11 +619,11 @@ export class RoomConnection implements RoomConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitGlobalMessage(message: PlayGlobalMessageInterface) {
|
public emitGlobalMessage(message: PlayGlobalMessageInterface): void {
|
||||||
const playGlobalMessage = new PlayGlobalMessage();
|
const playGlobalMessage = new PlayGlobalMessage();
|
||||||
playGlobalMessage.setId(message.id);
|
|
||||||
playGlobalMessage.setType(message.type);
|
playGlobalMessage.setType(message.type);
|
||||||
playGlobalMessage.setMessage(message.message);
|
playGlobalMessage.setContent(message.content);
|
||||||
|
playGlobalMessage.setBroadcasttoworld(message.broadcastToWorld);
|
||||||
|
|
||||||
const clientToServerMessage = new ClientToServerMessage();
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
clientToServerMessage.setPlayglobalmessage(playGlobalMessage);
|
clientToServerMessage.setPlayglobalmessage(playGlobalMessage);
|
||||||
|
@ -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,10 +1,11 @@
|
|||||||
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;
|
||||||
@ -18,18 +19,25 @@ export class TextUtils {
|
|||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -107,7 +107,7 @@ export const createLoadingPromise = (
|
|||||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||||
const errorCallback = (file: { src: string }) => {
|
const errorCallback = (file: { src: string }) => {
|
||||||
if (file.src !== playerResourceDescriptor.img) return;
|
if (file.src !== playerResourceDescriptor.img) return;
|
||||||
console.error("failed loading player ressource: ", playerResourceDescriptor);
|
console.error("failed loading player resource: ", playerResourceDescriptor);
|
||||||
rej(playerResourceDescriptor);
|
rej(playerResourceDescriptor);
|
||||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
|
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
|
||||||
loadPlugin.off("loaderror", errorCallback);
|
loadPlugin.off("loaderror", errorCallback);
|
||||||
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { Subscription } from "rxjs";
|
import type { Subscription } from "rxjs";
|
||||||
import { GlobalMessageManager } from "../../Administration/GlobalMessageManager";
|
|
||||||
import { userMessageManager } from "../../Administration/UserMessageManager";
|
import { userMessageManager } from "../../Administration/UserMessageManager";
|
||||||
import { iframeListener } from "../../Api/IframeListener";
|
import { iframeListener } from "../../Api/IframeListener";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
@ -75,8 +74,6 @@ import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey }
|
|||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { EmoteManager } from "./EmoteManager";
|
import { EmoteManager } from "./EmoteManager";
|
||||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||||
import RenderTexture = Phaser.GameObjects.RenderTexture;
|
|
||||||
import Tilemap = Phaser.Tilemaps.Tilemap;
|
|
||||||
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||||
|
|
||||||
import AnimatedTiles from "phaser-animated-tiles";
|
import AnimatedTiles from "phaser-animated-tiles";
|
||||||
@ -85,10 +82,12 @@ import { soundManager } from "./SoundManager";
|
|||||||
import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore";
|
import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||||
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
||||||
import { isMessageReferenceEvent, isTriggerMessageEvent } from "../../Api/Events/ui/TriggerMessageEvent";
|
|
||||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
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 { userIsAdminStore } from "../../Stores/GameStore";
|
||||||
|
import { isMessageReferenceEvent, isTriggerMessageEvent } from "../../Api/Events/ui/TriggerMessageEvent";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -156,7 +155,6 @@ export class GameScene extends DirtyScene {
|
|||||||
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
||||||
public connection: RoomConnection | undefined;
|
public connection: RoomConnection | undefined;
|
||||||
private simplePeer!: SimplePeer;
|
private simplePeer!: SimplePeer;
|
||||||
private GlobalMessageManager!: GlobalMessageManager;
|
|
||||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||||
private connectionAnswerPromiseResolve!: (
|
private connectionAnswerPromiseResolve!: (
|
||||||
value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>
|
value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>
|
||||||
@ -195,7 +193,7 @@ export class GameScene extends DirtyScene {
|
|||||||
private popUpElements: Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
|
private popUpElements: Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
|
||||||
private originalMapUrl: string | undefined;
|
private originalMapUrl: string | undefined;
|
||||||
private pinchManager: PinchManager | undefined;
|
private pinchManager: PinchManager | undefined;
|
||||||
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
private mapTransitioning: boolean = false; //used to prevent transitions happening at the same time.
|
||||||
private emoteManager!: EmoteManager;
|
private emoteManager!: EmoteManager;
|
||||||
private preloading: boolean = true;
|
private preloading: boolean = true;
|
||||||
private startPositionCalculator!: StartPositionCalculator;
|
private startPositionCalculator!: StartPositionCalculator;
|
||||||
@ -222,6 +220,9 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
//hook preload scene
|
//hook preload scene
|
||||||
preload(): void {
|
preload(): void {
|
||||||
|
//initialize frame event of scripting API
|
||||||
|
this.listenToIframeEvents();
|
||||||
|
|
||||||
const localUser = localUserStore.getLocalUser();
|
const localUser = localUserStore.getLocalUser();
|
||||||
const textures = localUser?.textures;
|
const textures = localUser?.textures;
|
||||||
if (textures) {
|
if (textures) {
|
||||||
@ -437,7 +438,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.characterLayers = gameManager.getCharacterLayers();
|
this.characterLayers = gameManager.getCharacterLayers();
|
||||||
this.companion = gameManager.getCompanion();
|
this.companion = gameManager.getCompanion();
|
||||||
|
|
||||||
//initalise map
|
//initialise map
|
||||||
this.Map = this.add.tilemap(this.MapUrlFile);
|
this.Map = this.add.tilemap(this.MapUrlFile);
|
||||||
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
|
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
|
||||||
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
||||||
@ -550,7 +551,6 @@ export class GameScene extends DirtyScene {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.triggerOnMapLayerPropertyChange();
|
this.triggerOnMapLayerPropertyChange();
|
||||||
this.listenToIframeEvents();
|
|
||||||
|
|
||||||
if (!this.room.isDisconnected()) {
|
if (!this.room.isDisconnected()) {
|
||||||
this.connect();
|
this.connect();
|
||||||
@ -605,6 +605,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,
|
||||||
@ -691,7 +693,6 @@ export class GameScene extends DirtyScene {
|
|||||||
peerStore.connectToSimplePeer(this.simplePeer);
|
peerStore.connectToSimplePeer(this.simplePeer);
|
||||||
screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
|
screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
|
||||||
videoFocusStore.connectToSimplePeer(this.simplePeer);
|
videoFocusStore.connectToSimplePeer(this.simplePeer);
|
||||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
|
||||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
@ -1082,8 +1083,74 @@ ${escapedMessage}
|
|||||||
for (const eventTile of eventTiles) {
|
for (const eventTile of eventTiles) {
|
||||||
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
||||||
}
|
}
|
||||||
|
this.markDirty();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
|
||||||
|
return this.connectionAnswerPromise.then(() => {
|
||||||
|
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
|
||||||
|
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
|
||||||
|
let newFirstgid = 1;
|
||||||
|
const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1];
|
||||||
|
if (lastTileset) {
|
||||||
|
//If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset
|
||||||
|
newFirstgid = lastTileset.firstgid + lastTileset.tilecount;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.load.on("filecomplete-json-" + eventTileset.url, () => {
|
||||||
|
let jsonTileset = this.cache.json.get(eventTileset.url);
|
||||||
|
const imageUrl = jsonTilesetDir + "/" + jsonTileset.image;
|
||||||
|
this.load.image(imageUrl, imageUrl);
|
||||||
|
this.load.on("filecomplete-image-" + imageUrl, () => {
|
||||||
|
//Add the firstgid of the tileset to the json file
|
||||||
|
jsonTileset = { ...jsonTileset, firstgid: newFirstgid };
|
||||||
|
this.mapFile.tilesets.push(jsonTileset);
|
||||||
|
this.Map.tilesets.push(
|
||||||
|
new Tileset(
|
||||||
|
jsonTileset.name,
|
||||||
|
jsonTileset.firstgid,
|
||||||
|
jsonTileset.tileWidth,
|
||||||
|
jsonTileset.tileHeight,
|
||||||
|
jsonTileset.margin,
|
||||||
|
jsonTileset.spacing,
|
||||||
|
jsonTileset.tiles
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.Terrains.push(
|
||||||
|
this.Map.addTilesetImage(
|
||||||
|
jsonTileset.name,
|
||||||
|
imageUrl,
|
||||||
|
jsonTileset.tilewidth,
|
||||||
|
jsonTileset.tileheight,
|
||||||
|
jsonTileset.margin,
|
||||||
|
jsonTileset.spacing
|
||||||
|
)
|
||||||
|
);
|
||||||
|
//destroy the tilemapayer because they are unique and we need to reuse their key and layerdData
|
||||||
|
for (const layer of this.Map.layers) {
|
||||||
|
layer.tilemapLayer.destroy(false);
|
||||||
|
}
|
||||||
|
//Create a new GameMap with the changed file
|
||||||
|
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
||||||
|
//Destroy the colliders of the old tilemapLayer
|
||||||
|
this.physics.add.world.colliders.destroy();
|
||||||
|
//Create new colliders with the new GameMap
|
||||||
|
this.createCollisionWithPlayer();
|
||||||
|
//Create new trigger with the new GameMap
|
||||||
|
this.triggerOnMapLayerPropertyChange();
|
||||||
|
resolve(newFirstgid);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.load.on("loaderror", () => {
|
||||||
|
console.error("Error while loading " + eventTileset.url + ".");
|
||||||
|
reject(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.load.json(eventTileset.url, eventTileset.url);
|
||||||
|
this.load.start();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
iframeListener.registerAnswerer(
|
iframeListener.registerAnswerer(
|
||||||
"triggerMessage",
|
"triggerMessage",
|
||||||
@ -1171,7 +1238,7 @@ ${escapedMessage}
|
|||||||
let targetRoom: Room;
|
let targetRoom: Room;
|
||||||
try {
|
try {
|
||||||
targetRoom = await Room.createRoom(roomUrl);
|
targetRoom = await Room.createRoom(roomUrl);
|
||||||
} catch (e: unknown) {
|
} catch (e) {
|
||||||
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
||||||
this.mapTransitioning = false;
|
this.mapTransitioning = false;
|
||||||
return;
|
return;
|
||||||
@ -1224,6 +1291,8 @@ ${escapedMessage}
|
|||||||
this.peerStoreUnsubscribe();
|
this.peerStoreUnsubscribe();
|
||||||
this.chatVisibilityUnsubscribe();
|
this.chatVisibilityUnsubscribe();
|
||||||
this.biggestAvailableAreaStoreUnsubscribe();
|
this.biggestAvailableAreaStoreUnsubscribe();
|
||||||
|
iframeListener.unregisterAnswerer("getState");
|
||||||
|
iframeListener.unregisterAnswerer("loadTileset");
|
||||||
iframeListener.unregisterAnswerer("getMapData");
|
iframeListener.unregisterAnswerer("getMapData");
|
||||||
iframeListener.unregisterAnswerer("getState");
|
iframeListener.unregisterAnswerer("getState");
|
||||||
iframeListener.unregisterAnswerer("triggerMessage");
|
iframeListener.unregisterAnswerer("triggerMessage");
|
||||||
@ -1300,7 +1369,7 @@ ${escapedMessage}
|
|||||||
try {
|
try {
|
||||||
const room = await Room.createRoom(exitRoomPath);
|
const room = await Room.createRoom(exitRoomPath);
|
||||||
return gameManager.loadMap(room, this.scene);
|
return gameManager.loadMap(room, this.scene);
|
||||||
} catch (e: unknown) {
|
} catch (e) {
|
||||||
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ export class StartPositionCalculator {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param selectedLayer this is always the layer that is selected with the hash in the url
|
* @param selectedLayer this is always the layer that is selected with the hash in the url
|
||||||
* @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} didnt yield any start points
|
* @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} did not yield any start points
|
||||||
*/
|
*/
|
||||||
public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) {
|
public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) {
|
||||||
if (!selectedOrDefaultLayer) {
|
if (!selectedOrDefaultLayer) {
|
||||||
@ -73,7 +73,7 @@ export class StartPositionCalculator {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param selectedLayer this is always the layer that is selected with the hash in the url
|
* @param selectedLayer this is always the layer that is selected with the hash in the url
|
||||||
* @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} didnt yield any start points
|
* @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} did not yield any start points
|
||||||
*/
|
*/
|
||||||
private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface {
|
private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface {
|
||||||
const tiles = selectedOrDefaultLayer.data;
|
const tiles = selectedOrDefaultLayer.data;
|
||||||
|
@ -244,6 +244,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
update(time: number, delta: number): void {
|
update(time: number, delta: number): void {
|
||||||
if (this.lazyloadingAttempt) {
|
if (this.lazyloadingAttempt) {
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
|
this.doMoveCursorHorizontally(this.moveHorizontally);
|
||||||
this.lazyloadingAttempt = false;
|
this.lazyloadingAttempt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import {enableUserInputsStore} from "../../Stores/UserInputStore";
|
|||||||
|
|
||||||
interface UserInputManagerDatum {
|
interface UserInputManagerDatum {
|
||||||
keyInstance: Phaser.Input.Keyboard.Key;
|
keyInstance: Phaser.Input.Keyboard.Key;
|
||||||
event: UserInputEvent
|
event: UserInputEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserInputEvent {
|
export enum UserInputEvent {
|
||||||
@ -20,8 +20,7 @@ export enum UserInputEvent {
|
|||||||
JoystickMove,
|
JoystickMove,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//we cannot use a map structure so we have to create a replacement
|
||||||
//we cannot use a map structure so we have to create a replacment
|
|
||||||
export class ActiveEventList {
|
export class ActiveEventList {
|
||||||
private eventMap: Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>();
|
private eventMap: Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>();
|
||||||
|
|
||||||
@ -61,8 +60,8 @@ export class UserInputManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enableUserInputsStore.subscribe((enable) => {
|
enableUserInputsStore.subscribe((enable) => {
|
||||||
enable ? this.restoreControls() : this.disableControls()
|
enable ? this.restoreControls() : this.disableControls();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initVirtualJoystick() {
|
initVirtualJoystick() {
|
||||||
@ -93,23 +92,65 @@ export class UserInputManager {
|
|||||||
|
|
||||||
initKeyBoardEvent() {
|
initKeyBoardEvent() {
|
||||||
this.KeysCode = [
|
this.KeysCode = [
|
||||||
{event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z, false) },
|
{
|
||||||
{event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W, false) },
|
event: UserInputEvent.MoveUp,
|
||||||
{event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q, false) },
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z, false),
|
||||||
{event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A, false) },
|
},
|
||||||
{event: UserInputEvent.MoveDown, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S, false) },
|
{
|
||||||
{event: UserInputEvent.MoveRight, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D, false) },
|
event: UserInputEvent.MoveUp,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveLeft,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveLeft,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveDown,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveRight,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D, false),
|
||||||
|
},
|
||||||
|
|
||||||
{event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP, false) },
|
{
|
||||||
{event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT, false) },
|
event: UserInputEvent.MoveUp,
|
||||||
{event: UserInputEvent.MoveDown, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN, false) },
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP, false),
|
||||||
{event: UserInputEvent.MoveRight, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT, false) },
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveLeft,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveDown,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.MoveRight,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT, false),
|
||||||
|
},
|
||||||
|
|
||||||
{event: UserInputEvent.SpeedUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT, false) },
|
{
|
||||||
|
event: UserInputEvent.SpeedUp,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT, false),
|
||||||
|
},
|
||||||
|
|
||||||
{event: UserInputEvent.Interact, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E, false) },
|
{
|
||||||
{event: UserInputEvent.Interact, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false) },
|
event: UserInputEvent.Interact,
|
||||||
{event: UserInputEvent.Shout, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false) },
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.Interact,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.Shout,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +196,7 @@ export class UserInputManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
eventsMap.set(UserInputEvent.JoystickMove, this.joystickEvents.any());
|
eventsMap.set(UserInputEvent.JoystickMove, this.joystickEvents.any());
|
||||||
this.KeysCode.forEach(d => {
|
this.KeysCode.forEach((d) => {
|
||||||
if (d.keyInstance.isDown) {
|
if (d.keyInstance.isDown) {
|
||||||
eventsMap.set(d.event, true);
|
eventsMap.set(d.event, true);
|
||||||
}
|
}
|
||||||
@ -164,17 +205,17 @@ export class UserInputManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spaceEvent(callback: Function) {
|
spaceEvent(callback: Function) {
|
||||||
this.Scene.input.keyboard.on('keyup-SPACE', (event: Event) => {
|
this.Scene.input.keyboard.on("keyup-SPACE", (event: Event) => {
|
||||||
callback();
|
callback();
|
||||||
return event;
|
return event;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addSpaceEventListner(callback: Function) {
|
addSpaceEventListner(callback: Function) {
|
||||||
this.Scene.input.keyboard.addListener('keyup-SPACE', callback);
|
this.Scene.input.keyboard.addListener("keyup-SPACE", callback);
|
||||||
}
|
}
|
||||||
removeSpaceEventListner(callback: Function) {
|
removeSpaceEventListner(callback: Function) {
|
||||||
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback);
|
this.Scene.input.keyboard.removeListener("keyup-SPACE", callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
@ -182,8 +223,11 @@ export class UserInputManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initMouseWheel() {
|
private initMouseWheel() {
|
||||||
this.Scene.input.on('wheel', (pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => {
|
this.Scene.input.on(
|
||||||
this.Scene.zoomByFactor(1 - deltaY / 53 * 0.1);
|
"wheel",
|
||||||
});
|
(pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => {
|
||||||
|
this.Scene.zoomByFactor(1 - (deltaY / 53) * 0.1);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,3 +3,5 @@ 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();
|
||||||
|
5
front/src/Stores/TypeMessageStore/BanMessageStore.ts
Normal file
5
front/src/Stores/TypeMessageStore/BanMessageStore.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const banMessageVisibleStore = writable(false);
|
||||||
|
|
||||||
|
export const banMessageContentStore = writable("");
|
5
front/src/Stores/TypeMessageStore/TextMessageStore.ts
Normal file
5
front/src/Stores/TypeMessageStore/TextMessageStore.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const textMessageVisibleStore = writable(false);
|
||||||
|
|
||||||
|
export const textMessageContentStore = writable("");
|
@ -6,21 +6,22 @@ export enum GameConnexionTypes {
|
|||||||
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;
|
||||||
@ -36,7 +37,7 @@ class UrlManager {
|
|||||||
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 {
|
||||||
|
@ -31,7 +31,7 @@ export class HtmlUtils {
|
|||||||
return p.innerHTML;
|
return p.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static urlify(text: string): string {
|
public static urlify(text: string, style: string = ""): string {
|
||||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||||
text = HtmlUtils.escapeHtml(text);
|
text = HtmlUtils.escapeHtml(text);
|
||||||
return text.replace(urlRegex, (url: string) => {
|
return text.replace(urlRegex, (url: string) => {
|
||||||
@ -40,10 +40,19 @@ export class HtmlUtils {
|
|||||||
link.target = "_blank";
|
link.target = "_blank";
|
||||||
const text = document.createTextNode(url);
|
const text = document.createTextNode(url);
|
||||||
link.appendChild(text);
|
link.appendChild(text);
|
||||||
|
link.setAttribute("style", style);
|
||||||
return link.outerHTML;
|
return link.outerHTML;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isClickedInside(event: MouseEvent, target: HTMLElement): boolean {
|
||||||
|
return !!event.composedPath().find((et) => et === target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isClickedOutside(event: MouseEvent, target: HTMLElement): boolean {
|
||||||
|
return !this.isClickedInside(event, target);
|
||||||
|
}
|
||||||
|
|
||||||
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
|
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
|
||||||
return elem !== null;
|
return elem !== null;
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ export class SimplePeer {
|
|||||||
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
||||||
peer.destroy();
|
peer.destroy();
|
||||||
|
|
||||||
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion
|
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
||||||
/*if(!this.PeerScreenSharingConnectionArray.delete(userId)){
|
/*if(!this.PeerScreenSharingConnectionArray.delete(userId)){
|
||||||
throw 'Couln\'t delete peer screen sharing connexion';
|
throw 'Couln\'t delete peer screen sharing connexion';
|
||||||
}*/
|
}*/
|
||||||
@ -370,14 +370,14 @@ export class SimplePeer {
|
|||||||
console.error(
|
console.error(
|
||||||
'Could not find peer whose ID is "' + data.userId + '" in receiveWebrtcScreenSharingSignal'
|
'Could not find peer whose ID is "' + data.userId + '" in receiveWebrtcScreenSharingSignal'
|
||||||
);
|
);
|
||||||
console.info("Attempt to create new peer connexion");
|
console.info("Attempt to create new peer connection");
|
||||||
if (stream) {
|
if (stream) {
|
||||||
this.sendLocalScreenSharingStreamToUser(data.userId, stream);
|
this.sendLocalScreenSharingStreamToUser(data.userId, stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
|
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
|
||||||
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion
|
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
||||||
//this.PeerScreenSharingConnectionArray.delete(data.userId);
|
//this.PeerScreenSharingConnectionArray.delete(data.userId);
|
||||||
this.receiveWebrtcScreenSharingSignal(data);
|
this.receiveWebrtcScreenSharingSignal(data);
|
||||||
}
|
}
|
||||||
@ -485,7 +485,7 @@ export class SimplePeer {
|
|||||||
|
|
||||||
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
|
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
|
||||||
PeerConnectionScreenSharing.destroy();
|
PeerConnectionScreenSharing.destroy();
|
||||||
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion
|
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
||||||
//this.PeerScreenSharingConnectionArray.delete(userId);
|
//this.PeerScreenSharingConnectionArray.delete(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
31
front/style/inputTextGlobalMessageSvelte-Style.scss
Normal file
31
front/style/inputTextGlobalMessageSvelte-Style.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//InputTextGlobalMessage
|
||||||
|
section.section-input-send-text {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.ql-toolbar{
|
||||||
|
max-height: 100px;
|
||||||
|
|
||||||
|
background: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.input-send-text{
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
color: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
.ql-editor.ql-blank::before {
|
||||||
|
color: whitesmoke;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-tooltip {
|
||||||
|
top: 40% !important;
|
||||||
|
left: 20% !important;
|
||||||
|
|
||||||
|
color: whitesmoke;
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,26 +3,26 @@ import {getRessourceDescriptor} from "../../../src/Phaser/Entity/PlayerTexturesL
|
|||||||
|
|
||||||
describe("getRessourceDescriptor()", () => {
|
describe("getRessourceDescriptor()", () => {
|
||||||
it(", if given a valid descriptor as parameter, should return it", () => {
|
it(", if given a valid descriptor as parameter, should return it", () => {
|
||||||
const desc = getRessourceDescriptor({name: 'name', img: 'url'});
|
const desc = getRessourceDescriptor({ name: "name", img: "url" });
|
||||||
expect(desc.name).toEqual('name');
|
expect(desc.name).toEqual("name");
|
||||||
expect(desc.img).toEqual('url');
|
expect(desc.img).toEqual("url");
|
||||||
});
|
});
|
||||||
|
|
||||||
it(", if given a string as parameter, should search trough hardcoded values", () => {
|
it(", if given a string as parameter, should search through hardcoded values", () => {
|
||||||
const desc = getRessourceDescriptor('male1');
|
const desc = getRessourceDescriptor("male1");
|
||||||
expect(desc.name).toEqual('male1');
|
expect(desc.name).toEqual("male1");
|
||||||
expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png");
|
expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
it(", if given a string as parameter, should search trough hardcoded values (bis)", () => {
|
it(", if given a string as parameter, should search through hardcoded values (bis)", () => {
|
||||||
const desc = getRessourceDescriptor('color_2');
|
const desc = getRessourceDescriptor("color_2");
|
||||||
expect(desc.name).toEqual('color_2');
|
expect(desc.name).toEqual("color_2");
|
||||||
expect(desc.img).toEqual("resources/customisation/character_color/character_color1.png");
|
expect(desc.img).toEqual("resources/customisation/character_color/character_color1.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
it(", if given a descriptor without url as parameter, should search trough hardcoded values", () => {
|
it(", if given a descriptor without url as parameter, should search through hardcoded values", () => {
|
||||||
const desc = getRessourceDescriptor({name: 'male1', img: ''});
|
const desc = getRessourceDescriptor({ name: "male1", img: "" });
|
||||||
expect(desc.name).toEqual('male1');
|
expect(desc.name).toEqual("male1");
|
||||||
expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png");
|
expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png");
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -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"
|
||||||
@ -3463,6 +3475,11 @@ lodash.clonedeep@^4.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
||||||
|
|
||||||
lodash.truncate@^4.4.2:
|
lodash.truncate@^4.4.2:
|
||||||
version "4.4.2"
|
version "4.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||||
@ -4485,6 +4502,13 @@ queue-typescript@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
linked-list-typescript "^1.0.11"
|
linked-list-typescript "^1.0.11"
|
||||||
|
|
||||||
|
quill-delta-to-html@^0.12.0:
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/quill-delta-to-html/-/quill-delta-to-html-0.12.0.tgz#ee572cfa208390d99d7646795358ddb81eaea17c"
|
||||||
|
integrity sha512-Yy6U2e7ov+ZlrFbj5/GbqOBCRjyNu+vuphy0Pk+7668zIMTVHJglZY2JNa++1/zkSiqptPBmP/CpsDzC4Wznsw==
|
||||||
|
dependencies:
|
||||||
|
lodash.isequal "^4.5.0"
|
||||||
|
|
||||||
quill-delta@^3.6.2:
|
quill-delta@^3.6.2:
|
||||||
version "3.6.3"
|
version "3.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
|
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
|
||||||
@ -5775,11 +5799,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"
|
||||||
|
@ -176,7 +176,7 @@ Tuomo Untinen CC-BY-3.0
|
|||||||
|
|
||||||
Casper Nilsson
|
Casper Nilsson
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
- Asain themed shrine including red lantern
|
- Asian themed shrine including red lantern
|
||||||
- foodog statue
|
- foodog statue
|
||||||
- Toro
|
- Toro
|
||||||
- Cherry blossom tree
|
- Cherry blossom tree
|
||||||
|
159
maps/tests/LoadTileset/LoadTileset.json
Normal file
159
maps/tests/LoadTileset/LoadTileset.json
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":10,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":1,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[33, 34, 33, 34, 34, 34, 35, 37, 38, 39, 41, 42, 41, 42, 42, 42, 43, 45, 46, 47, 33, 34, 60, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 49, 50, 50, 50, 50, 50, 51, 53, 54, 55],
|
||||||
|
"height":10,
|
||||||
|
"id":2,
|
||||||
|
"name":"bottom",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 59, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":3,
|
||||||
|
"name":"openwebsite",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":4,
|
||||||
|
"nextobjectid":1,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"scriptTileset.js"
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.7.0",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":8,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset_dungeon.png",
|
||||||
|
"imageheight":256,
|
||||||
|
"imagewidth":256,
|
||||||
|
"margin":0,
|
||||||
|
"name":"Dungeon",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":64,
|
||||||
|
"tileheight":32,
|
||||||
|
"tiles":[
|
||||||
|
{
|
||||||
|
"id":36,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":37,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":38,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":44,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":45,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":46,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":52,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":53,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":54,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":"1.6",
|
||||||
|
"width":10
|
||||||
|
}
|
BIN
maps/tests/LoadTileset/Yellow.jpg
Normal file
BIN
maps/tests/LoadTileset/Yellow.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
106
maps/tests/LoadTileset/Yellow.json
Normal file
106
maps/tests/LoadTileset/Yellow.json
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
{ "columns":11,
|
||||||
|
"image":"Yellow.jpg",
|
||||||
|
"imageheight":128,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"Yellow",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":44,
|
||||||
|
"tiledversion":"1.7.0",
|
||||||
|
"tileheight":32,
|
||||||
|
"tiles":[
|
||||||
|
{
|
||||||
|
"id":0,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"name",
|
||||||
|
"type":"string",
|
||||||
|
"value":"Mur"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":2,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":11,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":12,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"name",
|
||||||
|
"type":"string",
|
||||||
|
"value":"sol"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":13,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":22,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":23,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":24,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"tileset",
|
||||||
|
"version":"1.6"
|
||||||
|
}
|
6
maps/tests/LoadTileset/scriptTileset.js
Normal file
6
maps/tests/LoadTileset/scriptTileset.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
WA.room.loadTileset("http://maps.workadventure.localhost/tests/LoadTileset/Yellow.json").then((firstgid) => {
|
||||||
|
WA.room.setTiles([
|
||||||
|
{x: 5, y: 5, tile: firstgid + 1, layer: 'bottom'},
|
||||||
|
{x: 5, y: 3, tile: 'sol', layer: 'bottom'}
|
||||||
|
]);
|
||||||
|
});
|
BIN
maps/tests/LoadTileset/tileset_dungeon.png
Normal file
BIN
maps/tests/LoadTileset/tileset_dungeon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
@ -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
|
||||||
},
|
},
|
||||||
|
@ -123,9 +123,9 @@ message VariableWithTagMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message PlayGlobalMessage {
|
message PlayGlobalMessage {
|
||||||
string id = 1;
|
string type = 1;
|
||||||
string type = 2;
|
string content = 2;
|
||||||
string message = 3;
|
bool broadcastToWorld = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StopGlobalMessage {
|
message StopGlobalMessage {
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,6 +445,7 @@ message AdminMessage {
|
|||||||
message AdminRoomMessage {
|
message AdminRoomMessage {
|
||||||
string message = 1;
|
string message = 1;
|
||||||
string roomId = 2;
|
string roomId = 2;
|
||||||
|
string type = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A message sent by an administrator to absolutely everybody
|
// A message sent by an administrator to absolutely everybody
|
||||||
|
@ -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;
|
||||||
@ -118,6 +123,18 @@ class AdminApi {
|
|||||||
return data.data;
|
return data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> {
|
||||||
|
if (!ADMIN_API_URL) {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
||||||
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
|
}).then((data) => {
|
||||||
|
return data.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adminApi = new AdminApi();
|
export const adminApi = new AdminApi();
|
||||||
|
@ -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();
|
@ -1,44 +1,45 @@
|
|||||||
import { PusherRoom } from "../Model/PusherRoom";
|
import { PusherRoom } from "../Model/PusherRoom";
|
||||||
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
||||||
import {
|
import {
|
||||||
|
AdminMessage,
|
||||||
|
AdminPusherToBackMessage,
|
||||||
|
AdminRoomMessage,
|
||||||
|
BanMessage,
|
||||||
|
CharacterLayerMessage,
|
||||||
|
EmoteEventMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
GroupDeleteMessage,
|
GroupDeleteMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
|
JoinRoomMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
|
PusherToBackMessage,
|
||||||
|
QueryJitsiJwtMessage,
|
||||||
|
RefreshRoomMessage,
|
||||||
|
ReportPlayerMessage,
|
||||||
RoomJoinedMessage,
|
RoomJoinedMessage,
|
||||||
|
SendJitsiJwtMessage,
|
||||||
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
SubMessage,
|
SubMessage,
|
||||||
ReportPlayerMessage,
|
UserJoinedRoomMessage,
|
||||||
UserLeftMessage,
|
UserLeftMessage,
|
||||||
|
UserLeftRoomMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
ViewportMessage,
|
ViewportMessage,
|
||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage,
|
||||||
QueryJitsiJwtMessage,
|
|
||||||
SendJitsiJwtMessage,
|
|
||||||
JoinRoomMessage,
|
|
||||||
CharacterLayerMessage,
|
|
||||||
PusherToBackMessage,
|
|
||||||
WorldFullMessage,
|
|
||||||
WorldConnexionMessage,
|
WorldConnexionMessage,
|
||||||
AdminPusherToBackMessage,
|
TokenExpiredMessage,
|
||||||
ServerToAdminClientMessage,
|
|
||||||
EmoteEventMessage,
|
|
||||||
UserJoinedRoomMessage,
|
|
||||||
UserLeftRoomMessage,
|
|
||||||
AdminMessage,
|
|
||||||
BanMessage,
|
|
||||||
RefreshRoomMessage,
|
|
||||||
EmotePromptMessage,
|
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
|
WorldFullMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { ADMIN_API_URL, JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import { adminApi } from "./AdminApi";
|
import { adminApi } from "./AdminApi";
|
||||||
import { emitInBatch } from "./IoSocketHelpers";
|
import { emitInBatch } from "./IoSocketHelpers";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import { JITSI_URL } from "../Enum/EnvironmentVariable";
|
|
||||||
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||||
import { gaugeManager } from "./GaugeManager";
|
import { gaugeManager } from "./GaugeManager";
|
||||||
import { apiClientRepository } from "./ApiClientRepository";
|
import { apiClientRepository } from "./ApiClientRepository";
|
||||||
@ -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 {
|
||||||
@ -406,17 +389,6 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
|
||||||
if (!client.tags.includes("admin")) {
|
|
||||||
//In case of xss injection, we just kill the connection.
|
|
||||||
throw "Client is not an admin!";
|
|
||||||
}
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
|
|
||||||
|
|
||||||
client.backConnection.write(pusherToBackMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorlds(): Map<string, PusherRoom> {
|
public getWorlds(): Map<string, PusherRoom> {
|
||||||
return this.rooms;
|
return this.rooms;
|
||||||
}
|
}
|
||||||
@ -463,7 +435,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("An error occured while generating the Jitsi JWT token: ", e);
|
console.error("An error occurred while generating the Jitsi JWT token: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,8 +570,21 @@ 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();
|
||||||
@ -625,6 +610,36 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
client.backConnection.write(pusherToBackMessage);
|
client.backConnection.write(pusherToBackMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async emitPlayGlobalMessage(
|
||||||
|
client: ExSocketInterface,
|
||||||
|
playGlobalMessageEvent: PlayGlobalMessage
|
||||||
|
): Promise<void> {
|
||||||
|
if (!client.tags.includes("admin")) {
|
||||||
|
throw "Client is not an admin!";
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientRoomUrl = client.roomId;
|
||||||
|
let tabUrlRooms: string[];
|
||||||
|
|
||||||
|
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
||||||
|
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
|
||||||
|
} else {
|
||||||
|
tabUrlRooms = [clientRoomUrl];
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomMessage = new AdminRoomMessage();
|
||||||
|
roomMessage.setMessage(playGlobalMessageEvent.getContent());
|
||||||
|
roomMessage.setType(playGlobalMessageEvent.getType());
|
||||||
|
|
||||||
|
for (const roomUrl of tabUrlRooms) {
|
||||||
|
const apiRoom = await apiClientRepository.getClient(roomUrl);
|
||||||
|
roomMessage.setRoomid(roomUrl);
|
||||||
|
apiRoom.sendAdminMessageToRoom(roomMessage, (response) => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
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