Merge pull request #828 from thecodingmachine/develop

Deploy 2021-03-22
This commit is contained in:
David Négrier 2021-03-22 16:42:28 +01:00 committed by GitHub
commit f9a2097bc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1191 additions and 937 deletions

View File

@ -10,3 +10,6 @@ START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
# Keep empty if you are sharing hard coded / clear text credentials.
TURN_STATIC_AUTH_SECRET=
# The email address used by Let's encrypt to send renewal warnings (compulsory)
ACME_EMAIL=

View File

@ -25,13 +25,14 @@ docker-compose up
The environment will start.
You should now be able to browse to http://workadventure.localhost/ and see the application.
You should now be able to browse to http://play.workadventure.localhost/ and see the application.
You can view the dashboard at http://workadventure.localhost:8080/
Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
**/etc/hosts**
```
workadventure.localhost 127.0.0.1
127.0.0.1 workadventure.localhost
```
### MacOS developers, your environment with Vagrant

View File

@ -1,5 +1,4 @@
import {HttpRequest, HttpResponse} from "uWebSockets.js";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
import {HttpResponse} from "uWebSockets.js";
export class BaseController {

View File

@ -4,7 +4,6 @@ import {HttpRequest, HttpResponse} from "uWebSockets.js";
import { parse } from 'query-string';
import {App} from "../Server/sifrr.server";
import {socketManager} from "../Services/SocketManager";
import {ServerWritableStream} from "grpc";
export class DebugController {
constructor(private App : App) {

View File

@ -3,7 +3,6 @@ const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS)
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
const ADMIN_API_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 JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_ISS = process.env.JITSI_ISS || '';
@ -19,7 +18,6 @@ export {
ADMIN_API_TOKEN,
HTTP_PORT,
GRPC_PORT,
MAX_USERS_PER_ROOM,
GROUP_RADIUS,
ALLOW_ARTILLERY,
CPU_OVERHEAT_THRESHOLD,

View File

@ -7,7 +7,6 @@ import {PositionNotifier} from "./PositionNotifier";
import {Movable} from "_Model/Movable";
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
import {arrayIntersect} from "../Services/ArrayHelper";
import {MAX_USERS_PER_ROOM} from "../Enum/EnvironmentVariable";
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {ZoneSocket} from "src/RoomManager";
@ -116,8 +115,6 @@ export class GameRoom {
this.nextUserId++;
this.users.set(user.id, user);
this.usersByUuid.set(user.uuid, user);
// Let's call update position to trigger the join / leave room
//this.updatePosition(socket, userPosition);
this.updateUserGroup(user);
// Notify admins
@ -149,10 +146,6 @@ export class GameRoom {
}
}
get isFull(): boolean {
return this.users.size >= MAX_USERS_PER_ROOM;
}
public isEmpty(): boolean {
return this.users.size === 0 && this.admins.size === 0;
}

View File

@ -2,7 +2,8 @@ import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
import {
AdminGlobalMessage,
AdminMessage,
AdminPusherToBackMessage,
AdminPusherToBackMessage,
AdminRoomMessage,
BanMessage,
EmptyMessage,
ItemEventMessage,
@ -51,12 +52,8 @@ const roomManager: IRoomManagerServer = {
} else {
if (message.hasJoinroommessage()) {
throw new Error('Cannot call JoinRoomMessage twice!');
/*} else if (message.hasViewportmessage()) {
socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);*/
} else if (message.hasUsermovesmessage()) {
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
/*} else if (message.hasSetplayerdetailsmessage()) {
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);*/
} else if (message.hasSilentmessage()) {
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
@ -67,8 +64,6 @@ const roomManager: IRoomManagerServer = {
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
/*} else if (message.hasReportplayermessage()){
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
}else if (message.hasSendusermessage()) {
@ -119,10 +114,7 @@ const roomManager: IRoomManagerServer = {
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
call.end();
})
/*call.on('finish', () => {
debug('listenZone finish');
})*/
call.on('close', () => {
debug('listenZone connection closed');
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
@ -150,26 +142,6 @@ const roomManager: IRoomManagerServer = {
} else {
throw new Error('The first message sent MUST be of type JoinRoomMessage');
}
} else {
/*if (message.hasJoinroommessage()) {
throw new Error('Cannot call JoinRoomMessage twice!');
} else if (message.hasUsermovesmessage()) {
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
} else if (message.hasSilentmessage()) {
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
} else if (message.hasWebrtcsignaltoservermessage()) {
socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
} else {
throw new Error('Unhandled message type');
}*/
}
} catch (e) {
emitError(call, e);
@ -208,6 +180,10 @@ const roomManager: IRoomManagerServer = {
callback(null, new EmptyMessage());
},
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
callback(null, new EmptyMessage());
},
};
export {roomManager};

View File

@ -1,6 +1,5 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios";
import {v4} from "uuid";
export interface AdminApiData {
organizationSlug: string
@ -21,13 +20,6 @@ export interface CharacterTexture {
rights: string
}
export interface FetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
textures: CharacterTexture[];
messages: unknown[];
}
class AdminApi {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
@ -52,65 +44,6 @@ class AdminApi {
)
return res.data;
}
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
try {
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
} catch (e) {
if (e?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
return {
uuid: v4(),
tags: [],
textures: [],
messages: [],
}
} else {
throw e;
}
}
}
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) {
return Axios.post(`${ADMIN_API_URL}/api/report`, {
reportedUserUuid,
reportedUserComment,
reporterUserUuid,
reportWorldSlug,
},
{
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
}
}
export const adminApi = new AdminApi();

View File

@ -1,5 +1,4 @@
import {GameRoom} from "../Model/GameRoom";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {
ItemEventMessage,
ItemStateMessage,
@ -22,7 +21,11 @@ import {
Zone as ProtoZone,
BatchToPusherMessage,
SubToPusherMessage,
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage
UserJoinedZoneMessage,
GroupUpdateZoneMessage,
GroupLeftZoneMessage,
UserLeftZoneMessage,
BanUserMessage,
} from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -51,18 +54,6 @@ import crypto from "crypto";
const debug = Debug('sockermanager');
interface AdminSocketRoomsList {
[index: string]: number;
}
interface AdminSocketUsersList {
[index: string]: boolean;
}
export interface AdminSocketData {
rooms: AdminSocketRoomsList,
users: AdminSocketUsersList,
}
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
// TODO: should we batch those every 100ms?
const batchMessage = new BatchToPusherMessage();
@ -83,68 +74,13 @@ export class SocketManager {
});
}
/*getAdminSocketDataFor(roomId:string): AdminSocketData {
const data:AdminSocketData = {
rooms: {},
users: {},
}
const room = this.rooms.get(roomId);
if (room === undefined) {
return data;
}
const users = room.getUsers();
data.rooms[roomId] = users.size;
users.forEach(user => {
data.users[user.uuid] = true
})
return data;
}*/
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
/*const positionMessage = joinRoomMessage.getPositionmessage();
if (positionMessage === undefined) {
// TODO: send error message?
throw new Error('Empty pointMessage found in JoinRoomMessage');
}*/
//const position = ProtobufUtils.toPointInterface(positionMessage);
//const viewport = client.viewport;
//this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
//join new previous room
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
//const things = room.setViewport(client, viewport);
const roomJoinedMessage = new RoomJoinedMessage();
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
/*for (const thing of things) {
if (thing instanceof User) {
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
if (player === undefined) {
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
continue;
}
const userJoinedMessage = new UserJoinedMessage();
userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setName(player.name);
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
roomJoinedMessage.addUser(userJoinedMessage);
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
} else if (thing instanceof Group) {
const groupUpdateMessage = new GroupUpdateMessage();
groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
roomJoinedMessage.addGroup(groupUpdateMessage);
} else {
console.error("Unexpected type for Movable returned by setViewport");
}
}*/
for (const [itemId, item] of room.getItemsState().entries()) {
const itemStateMessage = new ItemStateMessage();
@ -158,8 +94,6 @@ export class SocketManager {
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
//user.socket.write(serverToClientMessage);
console.log('SENDING MESSAGE roomJoinedMessage');
socket.write(serverToClientMessage);
@ -168,13 +102,6 @@ export class SocketManager {
user
};
/*const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
}
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
@ -693,33 +620,6 @@ export class SocketManager {
}, 10000);
}
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/
static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] {
const characterLayerObjs: CharacterLayer[] = [];
for (const characterLayer of characterLayers) {
if (characterLayer.startsWith('customCharacterTexture')) {
const customCharacterLayerId: number = +characterLayer.substr(22);
for (const memberTexture of memberTextures) {
if (memberTexture.id == customCharacterLayerId) {
characterLayerObjs.push({
name: characterLayer,
url: memberTexture.url
})
break;
}
}
} else {
characterLayerObjs.push({
name: characterLayer,
url: undefined
})
}
}
return characterLayerObjs;
}
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
const room = this.rooms.get(roomId);
if (!room) {
@ -773,11 +673,6 @@ export class SocketManager {
public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise<GameRoom> {
const room = await socketManager.getOrCreateRoom(roomId);
// Dispatch groups position to newly connected user
/*world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(socket, group);
});*/
room.adminJoin(admin);
return room;
@ -807,7 +702,7 @@ export class SocketManager {
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('ban');
sendUserMessage.setType('ban'); //todo: is the type correct?
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
@ -842,6 +737,27 @@ export class SocketManager {
recipient.socket.write(serverToClientMessage);
recipient.socket.end();
}
sendAdminRoomMessage(roomId: string, message: string) {
const room = this.rooms.get(roomId);
if (!room) {
//todo: this should cause the http call to return a 500
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
return;
}
room.getUsers().forEach((recipient) => {
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('message');
const clientMessage = new ServerToClientMessage();
clientMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(clientMessage);
});
}
}
export const socketManager = new SocketManager();

View File

@ -10,7 +10,7 @@ services:
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.email=d.negrier@thecodingmachine.com
- --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/acme.json
# used during the challenge
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web

View File

@ -4,7 +4,7 @@
local tag = namespace,
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com",
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
local adminUrl = if namespace == "master" /*|| namespace == "develop"*/ || std.startsWith(namespace, "admin") then "https://"+url else null,
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"version": "1.0",
"containers": {
@ -23,9 +23,12 @@
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + if adminUrl != null then {
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
},
"back2": {
"image": "thecodingmachine/workadventure-back:"+tag,
@ -42,9 +45,12 @@
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + if adminUrl != null then {
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
},
"pusher": {
"replicas": 2,
@ -61,9 +67,12 @@
"JITSI_URL": env.JITSI_URL,
"API_URL": "back1:50051,back2:50051",
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + if adminUrl != null then {
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
},
"front": {
"image": "thecodingmachine/workadventure-front:"+tag,

View File

@ -1 +1,3 @@
index.html
index.html
index.tmpl.html.tmp
style.*.css

View File

@ -29,7 +29,6 @@
<base href="/">
<link rel="stylesheet" href="/resources/style/style.css">
<title>WorkAdventure</title>
</head>
<body id="body" style="margin: 0; background-color: #000">
@ -66,98 +65,54 @@
</div>
</div>
<div id="cowebsite" class="cowebsite hidden">
<button class="close-btn" id="cowebsite-close">
<aside id="cowebsite-aside">
<img src="/static/images/menu.svg" alt="hold to resize"/>
</aside>
<main id="cowebsite-main">
</main>
<button class="top-right-btn" id="cowebsite-fullscreen">
<img id="cowebsite-fullscreen-open" src="resources/logos/monitor.svg"/>
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/monitor-close.svg"/>
</button>
<button class="top-right-btn" id="cowebsite-close">
<img src="resources/logos/close.svg"/>
</button>
</div>
<div id="audioplayerctrl" class="hidden">
<div class="audioplayer">
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-volume-up" fill="white" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z" />
<g id="audioplayer_volume_icon_playing">
<path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z" />
<path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z" />
<path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z" />
</g>
</svg>
</button>
<div class="audioplayer">
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.05" value="1" />
</div>
</div>
<div class="audioplayer">
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
autoreduce
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
</label>
<div id="audioplayer" style="visibility: hidden"></div>
</div>
</div>
<div class="audio-playing">
<img src="/resources/logos/megaphone.svg"/>
<img src="/resources/logos/megaphone.svg" />
</div>
</div>
<div id="audioplayerctrl" class="hidden">
<div class="audioplayer">
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
class="bi bi-volume-up"
fill="white"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
/>
<g id="audioplayer_volume_icon_playing">
<path
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
/>
<path
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
/>
<path
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
/>
</g>
</svg>
</button>
<div class="audioplayer">
<input
type="range"
id="audioplayer_volume"
min="0"
max="1"
step="0.05"
value="1"
/>
</div>
</div>
<div class="audioplayer">
<label
id="label-audioplayer_decrease_while_talking"
for="audiooplayer_decrease_while_talking"
title="decrease background volume by 50% when entering conversations"
>
autoreduce
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
</label>
<div id="audioplayer" style="visibility: hidden"></div>
</div>
</div>
<div class="audio-playing">
<img src="/resources/logos/megaphone.svg" />
</div>
</div>
<!--
<div id="webRtc" class="webrtc">
<div id="activeCam" class="activeCam">
<div id="div-myCamVideo" class="video-container">
<video id="myCamVideo" autoplay muted></video>
</div>
</div>
<div class="btn-cam-action">
<div class="btn-micro">
<img id="microphone" src="resources/logos/microphone.svg">
<img id="microphone-close" src="resources/logos/microphone-close.svg">
</div>
<div class="btn-video">
<img id="cinema" src="resources/logos/cinema.svg">
<img id="cinema-close" src="resources/logos/cinema-close.svg">
</div>
<div class="btn-monitor">
<img id="monitor" src="resources/logos/monitor.svg">
<img id="monitor-close" src="resources/logos/monitor-close.svg">
</div>
</div>
</div>
-->
<div id="activeScreenSharing" class="active-screen-sharing active">
</div>
<div id="webRtcSetup" class="webrtcsetup">
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
<video id="myCamVideoSetup" autoplay muted></video>
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
<video id="myCamVideoSetup" autoplay muted></video>
</div>
<audio id="audio-webrtc-in">
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: url('/resources/logos/cursor_normal.png'), auto;
}
* a, button, select{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
#gameMenu button {
background-color: black;
color: white;

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: url('/resources/logos/cursor_normal.png'), auto;
}
* a, button, select{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
#menuIcon button {
background-color: black;
color: white;

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: url('/resources/logos/cursor_normal.png'), auto;
}
* a, button, select{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
#gameQuality {
background: #eceeee;
border: 1px solid #42464b;

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: url('/resources/logos/cursor_normal.png'), auto;
}
* a, button, input{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
#gameReport {
background: #eceeee;
border: 1px solid #42464b;

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: url('/resources/logos/cursor_normal.png'), auto;
}
* a, button, input{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
#gameShare {
background: #eceeee;
border: 1px solid #42464b;

View File

@ -0,0 +1,103 @@
<style>
#helpCameraSettings {
background: #eceeee;
border: 1px solid #42464b;
border-radius: 6px;
margin: 10px auto 0;
width: 400px;
height: 370px;
}
#helpCameraSettings h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#helpCameraSettings input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
}
#helpCameraSettings section {
margin: 10px;
}
#helpCameraSettings section.action{
text-align: center;
margin: 0;
}
#helpCameraSettings button {
margin-top: 10px;
background-color: black;
color: white;
border-radius: 7px;
padding-bottom: 4px;
}
#helpCameraSettings button#helpCameraSettingsFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#helpCameraSettings section a{
text-align: center;
font-size: 12px;
margin: 0 6px;
color: black;
}
#helpCameraSettings section h6,
#helpCameraSettings section h5{
margin: 1px;
}
#helpCameraSettings section.text-center{
text-align: center;
}
#helpCameraSettings section p{
font-size: 8px;
margin: 0px 20px;
}
#helpCameraSettings section p.err{
color: #ff0000;
}
#helpCameraSettings section ul{
margin: 6px;
}
#helpCameraSettings section li{
text-align: left;
font-size: 8px;
}
#helpCameraSettings section img {
width: 200px;
margin-top: 10px;
}
</style>
<form id="helpCameraSettings" hidden>
<section class="text-center">
<h5>Camera/Microphone access needed</h5>
<p class="err" id="permissionError">Permission denied</p>
<p class="info">You must allow camera and microphone access in your browser.</p>
<ul>
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
</ul>
<p class="info">Once you've followed these steps, please refresh this page.</p>
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
<p id='browserHelpSetting'></p>
</section>
<section class="action">
<button type="submit" id="helpCameraSettingsFormRefresh">Refresh</button>
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
</section>
</form>

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,170 @@
/* A potentially shared website could appear in an iframe in the cowebsite space. */
#cowebsite {
position: fixed;
transition: transform 0.5s;
background-color: white;
&.loading {
background-color: gray;
}
main {
iframe {
width: 100%;
height: 100%;
}
}
aside {
background: gray;
align-items: center;
display: flex;
img {
margin: 3px;
pointer-events: none;
height: 20px;
}
}
.top-right-btn{
position: absolute;
background: none;
border: none;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
img {
height: 20px;
background-color: rgba(0,0.0,0,0.3);
padding: 5px;
border-radius: 3px;
}
img:hover {
background-color: rgba(0,0,0,0.4);
}
}
}
@media (min-aspect-ratio: 1/1) {
#cowebsite {
right: 0;
top: 0;
width: 50%;
height: 100vh;
display: flex;
&.loading {
transform: translateX(90%);
}
&.hidden {
transform: translateX(100%);
}
main {
width: 100%;
}
aside {
width: 30px;
cursor: ew-resize;
img {
cursor: ew-resize;
transform: rotate(90deg);
}
}
.top-right-btn{
top: 10px;
right: -100px;
animation: right .2s ease;
img {
right: 15px;
}
}
#cowebsite-close {
right: -140px;
}
#cowebsite-fullscreen {
right: -100px;
}
}
#cowebsite:hover {
#cowebsite-close{
right: 10px;
}
#cowebsite-fullscreen{
right: 45px;
}
}
}
@media (max-aspect-ratio: 1/1) {
#cowebsite {
left: 0;
bottom: 0;
width: 100%;
height: 50%;
display: flex;
flex-direction: column;
&.loading {
transform: translateY(90%);
}
&.hidden {
transform: translateY(100%);
}
main {
height: 100%;
}
aside {
height: 30px;
cursor: ns-resize;
flex-direction: column;
img {
cursor: ns-resize;
}
}
.top-right-btn{
top: 10px;
right: -100px;
animation: right .2s ease;
img {
right: 15px;
}
}
#cowebsite-close {
right: -140px;
}
#cowebsite-fullscreen {
right: -100px;
}
}
#cowebsite:hover {
#cowebsite-close{
right: 10px;
}
#cowebsite-fullscreen{
right: 45px;
}
}
}

2
front/dist/resources/style/index.scss vendored Normal file
View File

@ -0,0 +1,2 @@
@import "cowebsite.scss";
@import "style.css";

View File

@ -325,35 +325,7 @@ body {
max-height: 25%;
}
#cowebsite {
right: 0;
top: 0;
width: 50%;
height: 100vh;
}
#cowebsite.loading {
transform: translateX(90%);
}
#cowebsite.hidden {
transform: translateX(100%);
}
#cowebsite .close-btn{
position: absolute;
top: 10px;
right: -100px;
background: none;
border: none;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
animation: right .2s ease;
}
#cowebsite .close-btn img{
height: 15px;
right: 15px;
}
#cowebsite:hover .close-btn{
right: 10px;
}
}
@media (max-aspect-ratio: 1/1) {
.game-overlay {
@ -372,19 +344,6 @@ body {
.sidebar > div:hover {
max-width: 25%;
}
#cowebsite {
left: 0;
bottom: 0;
width: 100%;
height: 50%;
}
#cowebsite.loading {
transform: translateY(90%);
}
#cowebsite.hidden {
transform: translateY(100%);
}
}
#game {
@ -392,21 +351,6 @@ body {
position: relative; /* Position relative is needed for the game-overlay. */
}
/* A potentially shared website could appear in an iframe in the cowebsite space. */
#cowebsite {
position: fixed;
transition: transform 0.5s;
background-color: white;
}
#cowebsite.loading {
background-color: gray;
}
#cowebsite > iframe {
width: 100%;
height: 100%;
}
.audioplayer:first-child {
display: grid;
grid: 2rem / 4rem 10rem;
@ -612,10 +556,9 @@ input[type=range]:focus::-ms-fill-upper {
}
.chat-mode {
display: flex;
display: grid;
width: 100%;
flex-wrap: wrap;
align-items: flex-start;
padding: 1%;
@ -631,24 +574,20 @@ input[type=range]:focus::-ms-fill-upper {
.chat-mode > div:hover {
margin: 0%;
}
.chat-mode.one-col > div {
flex-basis: 98%;
.chat-mode.one-col {
grid-template-columns: repeat(1, 1fr);
}
.chat-mode.two-col > div {
flex-basis: 48%;
.chat-mode.two-col {
grid-template-columns: repeat(2, 1fr);
}
.chat-mode.three-col > div {
flex-basis: 31.333333%;
.chat-mode.three-col {
grid-template-columns: repeat(3, 1fr);
}
.chat-mode.four-col > div {
flex-basis: 23%;
}
.chat-mode > div:last-child {
flex-grow: 5;
.chat-mode.four-col {
grid-template-columns: repeat(4, 1fr);
}
/*CONSOLE*/

View File

@ -9,9 +9,13 @@
"@types/quill": "^1.3.7",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"css-loader": "^5.1.3",
"eslint": "^6.8.0",
"html-webpack-plugin": "^4.3.0",
"jasmine": "^3.5.0",
"mini-css-extract-plugin": "^1.3.9",
"sass": "^1.32.8",
"sass-loader": "10.1.1",
"ts-loader": "^6.2.2",
"ts-node": "^8.10.2",
"typescript": "^3.8.3",

View File

@ -3,6 +3,7 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {RoomConnection} from "../Connexion/RoomConnection";
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
export const CLASS_CONSOLE_MESSAGE = 'main-console';
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
@ -10,13 +11,16 @@ export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
export const INPUT_TYPE_CONSOLE = 'input-type';
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
export const AUDIO_TYPE = 'audio';
export const MESSAGE_TYPE = 'message';
export const AUDIO_TYPE = AdminMessageEventTypes.audio;
export const MESSAGE_TYPE = AdminMessageEventTypes.admin;
interface EventTargetFiles extends EventTarget {
files: Array<File>;
}
/**
* @deprecated
*/
export class ConsoleGlobalMessageManager {
private readonly divMainConsole: HTMLDivElement;
@ -48,7 +52,7 @@ export class ConsoleGlobalMessageManager {
//this.buttonAdminMainConsole = document.createElement('img');
this.userInputManager = userInputManager;
this.initialise();
}
initialise() {
@ -140,7 +144,7 @@ export class ConsoleGlobalMessageManager {
const div = document.createElement('div');
div.id = INPUT_CONSOLE_MESSAGE
const buttonSend = document.createElement('button');
buttonSend.innerText = 'Envoyer';
buttonSend.innerText = 'Send';
buttonSend.classList.add('btn');
buttonSend.addEventListener('click', (event: MouseEvent) => {
this.sendMessage();
@ -242,7 +246,7 @@ export class ConsoleGlobalMessageManager {
div.appendChild(input);
const buttonSend = document.createElement('button');
buttonSend.innerText = 'Envoyer';
buttonSend.innerText = 'Send';
buttonSend.classList.add('btn');
buttonSend.addEventListener('click', (event: MouseEvent) => {
this.sendMessage();
@ -372,23 +376,6 @@ export class ConsoleGlobalMessageManager {
this.buttonSendMainConsole.classList.remove('active');
}
/*activeSettingConsole(){
this.activeSetting = true;
if(this.activeMessage){
this.disabledSettingConsole();
}
this.active();
this.divSettingConsole.classList.add('active');
//this.buttonSettingsMainConsole.classList.add('active');
}
disabledSettingConsole(){
this.activeSetting = false;
this.disabled();
this.divSettingConsole.classList.remove('active');
//this.buttonSettingsMainConsole.classList.remove('active');
}*/
private getSectionId(id: string) : string {
return `section-${id}`;
}

View File

@ -77,8 +77,10 @@ export class TypeMessageExt implements TypeMessageInterface{
}
}
}
export class Ban extends TypeMessageExt {
}
export class Message extends TypeMessageExt {}
export class Ban extends TypeMessageExt {}
export class Banned extends TypeMessageExt {
showMessage(message: string){

View File

@ -1,39 +1,29 @@
import {RoomConnection} from "../Connexion/RoomConnection";
import * as TypeMessages from "./TypeMessage";
import List = Phaser.Structs.List;
import {UpdatedLocalStreamCallback} from "../WebRtc/MediaManager";
import {Banned} from "./TypeMessage";
import {adminMessagesService} from "../Connexion/AdminMessagesService";
export interface TypeMessageInterface {
showMessage(message: string): void;
}
export class UserMessageManager {
class UserMessageManager {
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
receiveBannedMessageListener: Set<Function> = new Set<UpdatedLocalStreamCallback>();
receiveBannedMessageListener!: Function;
constructor(private Connection: RoomConnection) {
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);
});
this.initialise();
}
initialise() {
//receive signal to show message
this.Connection.receiveUserMessage((type: string, message: string) => {
const typeMessage = this.showMessage(type, message);
//listener on banned receive message
adminMessagesService.messageStream.subscribe((event) => {
const typeMessage = this.showMessage(event.type, event.text);
if(typeMessage instanceof Banned) {
for (const callback of this.receiveBannedMessageListener) {
callback();
}
this.receiveBannedMessageListener();
}
});
})
}
showMessage(type: string, message: string) {
@ -47,6 +37,7 @@ export class UserMessageManager {
}
setReceiveBanListener(callback: Function){
this.receiveBannedMessageListener.add(callback);
this.receiveBannedMessageListener = callback;
}
}
}
export const userMessageManager = new UserMessageManager()

View File

@ -0,0 +1,34 @@
import {Subject} from "rxjs";
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
export enum AdminMessageEventTypes {
admin = 'message',
audio = 'audio',
ban = 'ban',
}
interface AdminMessageEvent {
type: AdminMessageEventTypes,
text: string;
//todo add optional properties for other event types
}
//this class is designed to easily allow communication between the RoomConnection objects (that receive the message)
//and the various objects that may render the message on screen
class AdminMessagesService {
private _messageStream: Subject<AdminMessageEvent> = new Subject();
public messageStream = this._messageStream.asObservable();
constructor() {
this.messageStream.subscribe((event) => console.log('message', event))
}
onSendusermessage(message: SendUserMessage|BanUserMessage) {
this._messageStream.next({
type: message.getType() as unknown as AdminMessageEventTypes,
text: message.getMessage(),
})
}
}
export const adminMessagesService = new AdminMessagesService();

View File

@ -6,11 +6,22 @@ import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
import {localUserStore} from "./LocalUserStore";
import {LocalUser} from "./LocalUser";
import {Room} from "./Room";
import {Subject} from "rxjs";
export enum ConnexionMessageEventTypes {
worldFull = 1,
}
export interface ConnexionMessageEvent {
type: ConnexionMessageEventTypes,
}
class ConnectionManager {
private localUser!:LocalUser;
private connexionType?: GameConnexionTypes
public _connexionMessageStream:Subject<ConnexionMessageEvent> = new Subject();
/**
* Tries to login to the node server and return the starting map url to be loaded
*/

View File

@ -1,4 +1,4 @@
import {PlayerAnimationNames} from "../Phaser/Player/Animation";
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import {SignalData} from "simple-peer";
import {RoomConnection} from "./RoomConnection";
@ -42,14 +42,6 @@ export interface PointInterface {
moving: boolean;
}
export class Point implements PointInterface{
constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) {
if(x === null || y === null){
throw Error("position x and y cannot be null");
}
}
}
export interface MessageUserPositionInterface {
userId: number;
name: string;
@ -80,20 +72,10 @@ export interface GroupCreatedUpdatedMessageInterface {
groupSize: number
}
export interface WebRtcStartMessageInterface {
roomId: string,
clients: UserSimplePeerInterface[]
}
export interface WebRtcDisconnectMessageInterface {
userId: number
}
export interface WebRtcSignalSentMessageInterface {
receiverId: number,
signal: SignalData
}
export interface WebRtcSignalReceivedMessageInterface {
userId: number,
signal: SignalData,
@ -113,11 +95,6 @@ export interface ViewportInterface {
bottom: number,
}
export interface BatchedMessageInterface {
event: string,
payload: unknown
}
export interface ItemEventMessageInterface {
itemId: number,
event: string,

View File

@ -27,7 +27,7 @@ import {
SendJitsiJwtMessage,
CharacterLayerMessage,
PingMessage,
SendUserMessage
SendUserMessage, BanUserMessage
} from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -42,6 +42,8 @@ import {
WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
import {adminMessagesService} from "./AdminMessagesService";
import {connectionManager, ConnexionMessageEventTypes} from "./ConnectionManager";
const manualPingDelay = 20000;
@ -100,7 +102,7 @@ export class RoomConnection implements RoomConnection {
}
// If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry.
if (this.userId === null) {
if (this.userId === null && !this.closed) {
this.dispatch(EventMessage.CONNECTING_ERROR, event);
}
});
@ -140,8 +142,6 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasRoomjoinedmessage()) {
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
//const users: Array<MessageUserJoined> = roomJoinedMessage.getUserList().map(this.toMessageUserJoined.bind(this));
//const groups: Array<GroupCreatedUpdatedMessageInterface> = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage.bind(this));
const items: { [itemId: number] : unknown } = {};
for (const item of roomJoinedMessage.getItemList()) {
items[item.getItemid()] = JSON.parse(item.getStatejson());
@ -150,24 +150,15 @@ export class RoomConnection implements RoomConnection {
this.userId = roomJoinedMessage.getCurrentuserid();
this.tags = roomJoinedMessage.getTagList();
//console.log('Dispatching CONNECT')
this.dispatch(EventMessage.CONNECT, {
connection: this,
room: {
//users,
//groups,
items
} as RoomJoinedMessageInterface
});
/*console.log('Dispatching START_ROOM')
this.dispatch(EventMessage.START_ROOM, {
//users,
//groups,
items
});*/
} else if (message.hasErrormessage()) {
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage());
connectionManager._connexionMessageStream.next({type: ConnexionMessageEventTypes.worldFull}); //todo: generalize this behavior to all messages
this.closed = true;
} else if (message.hasWebrtcsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
@ -185,9 +176,9 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasSendjitsijwtmessage()) {
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
} else if (message.hasSendusermessage()) {
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
} else if (message.hasBanusermessage()) {
this.dispatch(EventMessage.USER_MESSAGE, message.getBanusermessage());
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
} else {
throw new Error('Unknown message received');
}
@ -541,12 +532,6 @@ export class RoomConnection implements RoomConnection {
});
}
public receiveUserMessage(callback: (type: string, message: string) => void) {
return this.onMessage(EventMessage.USER_MESSAGE, (message: SendUserMessage) => {
callback(message.getType(), message.getMessage());
});
}
public emitGlobalMessage(message: PlayGlobalMessageInterface){
const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id);

View File

@ -1,4 +1,4 @@
import {PlayerAnimationNames} from "../Player/Animation";
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
import {SpeechBubble} from "./SpeechBubble";
import BitmapText = Phaser.GameObjects.BitmapText;
import Container = Phaser.GameObjects.Container;
@ -10,8 +10,7 @@ interface AnimationData {
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
frames : number[]
}
export abstract class Character extends Container {
@ -19,7 +18,7 @@ export abstract class Character extends Container {
private readonly playerName: BitmapText;
public PlayerValue: string;
public sprites: Map<string, Sprite>;
private lastDirection: string = PlayerAnimationNames.WalkDown;
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
//private teleportation: Sprite;
private invisible: boolean;
@ -28,7 +27,7 @@ export abstract class Character extends Container {
y: number,
texturesPromise: Promise<string[]>,
name: string,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean,
frame?: string | number
) {
@ -81,7 +80,7 @@ export abstract class Character extends Container {
this.getPlayerAnimations(texture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {frames: d.frames}),
frameRate: d.frameRate,
repeat: d.repeat
});
@ -96,37 +95,57 @@ export abstract class Character extends Container {
private getPlayerAnimations(name: string): AnimationData[] {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frames: [0, 1, 2, 1],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frames: [3, 4, 5, 4],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frames: [6, 7, 8, 7],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
frames: [9, 10, 11, 10],
frameRate: 10,
repeat: -1
},{
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [1],
frameRate: 10,
repeat: 1
}, {
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [4],
frameRate: 10,
repeat: 1
}, {
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [7],
frameRate: 10,
repeat: 1
}, {
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [10],
frameRate: 10,
repeat: 1
}];
}
protected playAnimation(direction : string, moving: boolean): void {
protected playAnimation(direction : PlayerAnimationDirections, moving: boolean): void {
if (this.invisible) return;
for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) {
@ -134,10 +153,9 @@ export abstract class Character extends Container {
return;
}
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
sprite.play(texture+'-'+direction, true);
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true);
} else if (!moving) {
sprite.anims.play(texture + '-' + direction, true);
sprite.anims.stop();
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true);
}
}
}
@ -157,17 +175,17 @@ export abstract class Character extends Container {
// up or down animations are prioritized over left and right
if (body.velocity.y < 0) { //moving up
this.lastDirection = PlayerAnimationNames.WalkUp;
this.playAnimation(PlayerAnimationNames.WalkUp, true);
this.lastDirection = PlayerAnimationDirections.Up;
this.playAnimation(PlayerAnimationDirections.Up, true);
} else if (body.velocity.y > 0) { //moving down
this.lastDirection = PlayerAnimationNames.WalkDown;
this.playAnimation(PlayerAnimationNames.WalkDown, true);
this.lastDirection = PlayerAnimationDirections.Down;
this.playAnimation(PlayerAnimationDirections.Down, true);
} else if (body.velocity.x > 0) { //moving right
this.lastDirection = PlayerAnimationNames.WalkRight;
this.playAnimation(PlayerAnimationNames.WalkRight, true);
this.lastDirection = PlayerAnimationDirections.Right;
this.playAnimation(PlayerAnimationDirections.Right, true);
} else if (body.velocity.x < 0) { //moving left
this.lastDirection = PlayerAnimationNames.WalkLeft;
this.playAnimation(PlayerAnimationNames.WalkLeft, true);
this.lastDirection = PlayerAnimationDirections.Left;
this.playAnimation(PlayerAnimationDirections.Left, true);
}
this.setDepth(this.y);

View File

@ -1,6 +1,7 @@
import {GameScene} from "../Game/GameScene";
import {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import {PlayerAnimationDirections} from "../Player/Animation";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
@ -15,22 +16,17 @@ export class RemotePlayer extends Character {
y: number,
name: string,
texturesPromise: Promise<string[]>,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//set data
this.userId = userId;
//todo: implement on click action
/*this.playerName.setInteractive();
this.playerName.on('pointerup', () => {
});*/
}
updatePosition(position: PointInterface): void {
this.playAnimation(position.direction, position.moving);
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
this.setX(position.x);
this.setY(position.y);

View File

@ -2,6 +2,7 @@ import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {Room} from "../../Connexion/Room";
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
@ -78,6 +79,7 @@ export class GameManager {
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
scenePlugin.launch(MenuSceneName);
scenePlugin.launch(HelpCameraSettingsSceneName);//700
}
public gameSceneIsCreated(scene: GameScene) {

View File

@ -3,27 +3,17 @@ import {
GroupCreatedUpdatedMessageInterface,
MessageUserJoined,
MessageUserMovedInterface,
MessageUserPositionInterface, OnConnectInterface,
MessageUserPositionInterface,
OnConnectInterface,
PointInterface,
PositionInterface,
RoomJoinedMessageInterface
} from "../../Connexion/ConnexionModels";
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
import {
DEBUG_MODE,
JITSI_PRIVATE_MODE,
POSITION_DELAY,
RESOLUTION,
ZOOM_LEVEL
} from "../../Enum/EnvironmentVariable";
import {
ITiledMap,
ITiledMapLayer,
ITiledMapLayerProperty, ITiledMapObject,
ITiledTileSet
} from "../Map/ITiledMap";
import {DEBUG_MODE, JITSI_PRIVATE_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationNames} from "../Player/Animation";
import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer";
@ -41,11 +31,6 @@ import {
TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES
} from "../../WebRtc/LayoutManager";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager";
@ -54,10 +39,10 @@ import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager";
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {connectionManager, ConnexionMessageEvent, ConnexionMessageEventTypes} from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
import {UserMessageManager} from "../../Administration/UserMessageManager";
import {userMessageManager} from "../../Administration/UserMessageManager";
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
@ -72,7 +57,12 @@ import {TextureError} from "../../Exception/TextureError";
import {addLoader} from "../Components/Loader";
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {Subscription} from "rxjs";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -131,7 +121,6 @@ export class GameScene extends ResizableScene implements CenterListener {
public connection!: RoomConnection;
private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager;
private UserMessageManager!: UserMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
@ -159,11 +148,12 @@ export class GameScene extends ResizableScene implements CenterListener {
// The item that can be selected by pressing the space key.
private outlinedItem: ActionableItem|null = null;
public userInputManager!: UserInputManager;
private isReconnecting: boolean = false;
private isReconnecting: boolean|undefined = undefined;
private startLayerName!: string | null;
private openChatIcon!: OpenChatIcon;
private playerName!: string;
private characterLayers!: string[];
private messageSubscription: Subscription|null = null;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({
@ -295,25 +285,6 @@ export class GameScene extends ResizableScene implements CenterListener {
}
});
});
// import(/* webpackIgnore: true */ scriptUrl).then(result => {
//
// result.default.preload(this.load);
//
// this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends.
// this.load.on('complete', () => {
// // FIXME: the factory might fail because the resources might not be loaded yet...
// // We would need to add a loader ended event in addition to the createPromise
// this.createPromise.then(() => {
// result.default.create(this);
//
// for (let object of objectsOfType) {
// // TODO: we should pass here a factory to create sprites (maybe?)
// let objectSprite = result.default.factory(this, object);
// }
// });
// });
// });
}
}
@ -332,6 +303,8 @@ export class GameScene extends ResizableScene implements CenterListener {
gameManager.gameSceneIsCreated(this);
urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl();
this.messageSubscription = connectionManager._connexionMessageStream.subscribe((event) => this.onConnexionMessage(event))
const playerName = gameManager.getPlayerName();
if (!playerName) {
@ -403,13 +376,13 @@ export class GameScene extends ResizableScene implements CenterListener {
this.scene.launch(ReconnectingSceneName);
}, 0);
} else if (this.connection === undefined) {
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout(() => {
if (this.connection === undefined) {
this.scene.sleep();
this.scene.launch(ReconnectingSceneName);
}
}, 500);
}, 1000);
}
this.createPromiseResolve();
@ -537,8 +510,7 @@ export class GameScene extends ResizableScene implements CenterListener {
// When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
const self = this;
this.simplePeer.registerPeerConnectionListener({
@ -572,11 +544,10 @@ export class GameScene extends ResizableScene implements CenterListener {
//init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
return this.connection;
});
}
//todo: into dedicated classes
private initCirclesCanvas(): void {
// Let's generate the circle for the group delimiter
@ -610,30 +581,7 @@ export class GameScene extends ResizableScene implements CenterListener {
contextRed.stroke();
this.circleRedTexture.refresh();
}
private playAudio(url: string|number|boolean|undefined, loop=false): void {
if (url === undefined) {
audioManager.unloadAudio();
} else {
const audioPath = url as string;
let realAudioPath = '';
if (audioPath.indexOf('://') > 0) {
// remote file or stream
realAudioPath = audioPath;
} else {
// local file, include it relative to map directory
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
realAudioPath = mapDirUrl + '/' + url;
}
audioManager.loadAudio(realAudioPath);
if (loop) {
audioManager.loop();
}
}
}
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) {
try {
@ -657,7 +605,7 @@ export class GameScene extends ResizableScene implements CenterListener {
coWebsiteManager.closeCoWebsite();
}else{
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(newValue as string, allProps.get('openWebsitePolicy') as string | undefined);
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined);
layoutManager.removeActionButton('openWebsite', this.userInputManager);
};
@ -715,15 +663,19 @@ export class GameScene extends ResizableScene implements CenterListener {
}
});
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
this.playAudio(newValue);
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
});
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
this.playAudio(newValue, true);
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
});
}
private getMapDirUrl(): string {
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
}
private onMapExit(exitKey: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
@ -749,14 +701,11 @@ export class GameScene extends ResizableScene implements CenterListener {
// stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager.closeCoWebsite();
this.stopJitsi();
this.playAudio(undefined);
audioManager.unloadAudio();
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
if(this.connection) {
this.connection.closeConnection();
}
if(this.simplePeer) {
this.simplePeer.unregister();
}
this.connection?.closeConnection();
this.simplePeer?.unregister();
this.messageSubscription?.unsubscribe();
}
private removeAllRemotePlayers(): void {
@ -918,7 +867,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.startY,
this.playerName,
texturesPromise,
PlayerAnimationNames.WalkDown,
PlayerAnimationDirections.Down,
false,
this.userInputManager
);
@ -967,16 +916,16 @@ export class GameScene extends ResizableScene implements CenterListener {
let x = event.x;
let y = event.y;
switch (event.direction) {
case PlayerAnimationNames.WalkUp:
case PlayerAnimationDirections.Up:
y -= 32;
break;
case PlayerAnimationNames.WalkDown:
case PlayerAnimationDirections.Down:
y += 32;
break;
case PlayerAnimationNames.WalkLeft:
case PlayerAnimationDirections.Left:
x -= 32;
break;
case PlayerAnimationNames.WalkRight:
case PlayerAnimationDirections.Right:
x += 32;
break;
default:
@ -1047,13 +996,12 @@ export class GameScene extends ResizableScene implements CenterListener {
break;
}
}
// Let's move all users
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) {
throw new Error('Cannot find player with ID "' + userId +'"');
throw new Error('Cannot find player with ID "' + userId + '"');
}
player.updatePosition(moveEvent);
});
@ -1112,7 +1060,7 @@ export class GameScene extends ResizableScene implements CenterListener {
addPlayerData.position.y,
addPlayerData.name,
texturesPromise,
addPlayerData.position.direction,
addPlayerData.position.direction as PlayerAnimationDirections,
addPlayerData.position.moving
);
this.MapPlayers.add(player);
@ -1272,21 +1220,34 @@ export class GameScene extends ResizableScene implements CenterListener {
}
public stopJitsi(): void {
this.connection.setSilent(false);
this.connection?.setSilent(false);
jitsiFactory.stop();
mediaManager.showGameOverlay();
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
}
//todo: into onConnexionMessage
private bannedUser(){
this.cleanupClosingScene();
this.userInputManager.clearAllKeys();
this.scene.start(ErrorSceneName, {
title: 'Banned',
subTitle: 'You was banned of WorkAdventure',
message: 'If you want more information, you can contact us: workadventure@thecodingmachine.com'
subTitle: 'You were banned from WorkAdventure',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
});
}
private onConnexionMessage(event: ConnexionMessageEvent) {
if (event.type === ConnexionMessageEventTypes.worldFull) {
this.cleanupClosingScene();
this.scene.stop(ReconnectingSceneName);
this.userInputManager.clearAllKeys();
this.scene.start(ErrorSceneName, {
title: 'Connection rejected',
subTitle: 'The world you are trying to join is full. Try again later.',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
});
}
}
}

View File

@ -1,8 +1,6 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import {GameSceneInitInterface} from "../Game/GameScene";
import {StartMapInterface} from "../../Connexion/ConnexionModels";
import {mediaManager} from "../../WebRtc/MediaManager";
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
import {SoundMeter} from "../Components/SoundMeter";
@ -18,6 +16,7 @@ enum LoginTextures {
arrowUp = "arrow_up"
}
export class EnableCameraScene extends Phaser.Scene {
private textField!: TextField;
private pressReturnField!: TextField;

View File

@ -0,0 +1,93 @@
import {mediaManager} from "../../WebRtc/MediaManager";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
const helpCameraSettings = 'helpCameraSettings';
/**
* The scene that show how to permit Camera and Microphone access if there are not already allowed
*/
export class HelpCameraSettingsScene extends Phaser.Scene {
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
private helpCameraSettingsOpened: boolean = false;
constructor() {
super({key: HelpCameraSettingsSceneName});
}
preload() {
this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
}
create(){
this.createHelpCameraSettings();
}
private createHelpCameraSettings() : void {
const middleX = (window.innerWidth / 3) - (370*0.85);
this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
this.helpCameraSettingsElement.addListener('click');
this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
window.location.reload();
}else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
this.closeHelpCameraSettingsOpened();
}
});
if(!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video){
this.openHelpCameraSettingsOpened();
}
}
private openHelpCameraSettingsOpened(): void{
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.helpCameraSettingsOpened = true;
let middleY = (window.innerHeight / 3) - (495);
if(middleY < 0){
middleY = 0;
}
let middleX = (window.innerWidth / 3) - (370*0.85);
if(middleX < 0){
middleX = 0;
}
if(window.navigator.userAgent.includes('Firefox')){
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
}else if(window.navigator.userAgent.includes('Chrome')){
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
}
this.tweens.add({
targets: this.helpCameraSettingsElement,
y: middleY,
x: middleX,
duration: 1000,
ease: 'Power3',
overflow: 'scroll'
});
}
private closeHelpCameraSettingsOpened(): void{
const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
helpCameraSettingsInfo.innerText = '';
helpCameraSettingsInfo.style.display = 'none';
this.helpCameraSettingsOpened = false;
this.tweens.add({
targets: this.helpCameraSettingsElement,
y: -400,
duration: 1000,
ease: 'Power3',
overflow: 'scroll'
});
}
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
setTimeout(() => {
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
}, 250);
}
}

View File

@ -1,9 +1,13 @@
export enum PlayerAnimationNames {
WalkDown = 'down',
WalkLeft = 'left',
WalkUp = 'up',
WalkRight = 'right',
export enum PlayerAnimationDirections {
Down = 'down',
Left = 'left',
Up = 'up',
Right = 'right',
}
export enum PlayerAnimationTypes {
Walk = 'walk',
Idle = 'idle',
}

View File

@ -1,4 +1,4 @@
import {PlayerAnimationNames} from "./Animation";
import {PlayerAnimationDirections} from "./Animation";
import {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character";
@ -11,7 +11,7 @@ export interface CurrentGamerInterface extends Character{
}
export class Player extends Character implements CurrentGamerInterface {
private previousDirection: string = PlayerAnimationNames.WalkDown;
private previousDirection: string = PlayerAnimationDirections.Down;
private wasMoving: boolean = false;
constructor(
@ -20,7 +20,7 @@ export class Player extends Character implements CurrentGamerInterface {
y: number,
name: string,
texturesPromise: Promise<string[]>,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean,
private userInputManager: UserInputManager
) {
@ -43,20 +43,20 @@ export class Player extends Character implements CurrentGamerInterface {
let y = 0;
if (activeEvents.get(UserInputEvent.MoveUp)) {
y = - moveAmount;
direction = PlayerAnimationNames.WalkUp;
direction = PlayerAnimationDirections.Up;
moving = true;
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
y = moveAmount;
direction = PlayerAnimationNames.WalkDown;
direction = PlayerAnimationDirections.Down;
moving = true;
}
if (activeEvents.get(UserInputEvent.MoveLeft)) {
x = -moveAmount;
direction = PlayerAnimationNames.WalkLeft;
direction = PlayerAnimationDirections.Left;
moving = true;
} else if (activeEvents.get(UserInputEvent.MoveRight)) {
x = moveAmount;
direction = PlayerAnimationNames.WalkRight;
direction = PlayerAnimationDirections.Right;
moving = true;
}
if (x !== 0 || y !== 0) {

View File

@ -38,6 +38,25 @@ class AudioManager {
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
}
public playAudio(url: string|number|boolean, mapDirUrl: string, loop=false): void {
const audioPath = url as string;
let realAudioPath = '';
if (audioPath.indexOf('://') > 0) {
// remote file or stream
realAudioPath = audioPath;
} else {
// local file, include it relative to map directory
realAudioPath = mapDirUrl + '/' + url;
}
this.loadAudio(realAudioPath);
if (loop) {
this.loop();
}
}
private close(): void {
this.audioPlayerCtrl.classList.remove('loading');
this.audioPlayerCtrl.classList.add('hidden');
@ -75,7 +94,7 @@ class AudioManager {
}
public loadAudio(url: string): void {
private loadAudio(url: string): void {
this.load();
/* Solution 1, remove whole audio player */
@ -125,7 +144,7 @@ class AudioManager {
this.open();
}
public loop(): void {
private loop(): void {
if (this.audioPlayerElem !== undefined) {
this.audioPlayerElem.loop = true;
}

View File

@ -1,6 +1,5 @@
import {HtmlUtils} from "./HtmlUtils";
export type CoWebsiteStateChangedCallback = () => void;
import {Subject} from "rxjs";
enum iframeStates {
closed = 1,
@ -8,29 +7,96 @@ enum iframeStates {
opened,
}
const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe.
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
const cowebsiteCloseButtonId = 'cowebsite-close';
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
const animationTime = 500; //time used by the css transitions, in ms.
class CoWebsiteManager {
private opened: iframeStates = iframeStates.closed;
private observers = new Array<CoWebsiteStateChangedCallback>();
private _onResize: Subject<void> = new Subject();
public onResize = this._onResize.asObservable();
/**
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
* So we use this promise to queue up every cowebsite state transition
*/
private currentOperationPromise: Promise<void> = Promise.resolve();
private cowebsiteDiv: HTMLDivElement;
private resizing: boolean = false;
private cowebsiteMainDom: HTMLDivElement;
private cowebsiteAsideDom: HTMLDivElement;
get width(): number {
return this.cowebsiteDiv.clientWidth;
}
set width(width: number) {
this.cowebsiteDiv.style.width = width+'px';
}
get height(): number {
return this.cowebsiteDiv.clientHeight;
}
set height(height: number) {
this.cowebsiteDiv.style.height = height+'px';
}
get verticalMode(): boolean {
return window.innerWidth < window.innerHeight;
}
get isFullScreen(): boolean {
return this.verticalMode ? this.height === this.cowebsiteDiv.clientHeight : this.width === this.cowebsiteDiv.clientWidth
}
constructor() {
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteAsideDomId);
this.initResizeListeners();
HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => {
this.closeCoWebsite();
});
HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => {
this.fullscreen();
});
}
private initResizeListeners() {
const movecallback = (event:MouseEvent) => {
this.verticalMode ? this.height -= event.movementY : this.width -= event.movementX;
this.fire();
}
this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
this.resizing = true;
this.getIframeDom().style.display = 'none';
document.addEventListener('mousemove', movecallback);
});
document.addEventListener('mouseup', (event) => {
if (!this.resizing) return;
document.removeEventListener('mousemove', movecallback);
this.getIframeDom().style.display = 'block';
this.resizing = false;
});
}
private close(): void {
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
this.cowebsiteDiv.classList.add('hidden');
this.opened = iframeStates.closed;
this.resetStyle();
}
private load(): void {
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
@ -40,29 +106,34 @@ class CoWebsiteManager {
private open(): void {
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
this.opened = iframeStates.opened;
this.resetStyle();
}
public loadCoWebsite(url: string, allowPolicy?: string): void {
public resetStyle() {
this.cowebsiteDiv.style.width = '';
this.cowebsiteDiv.style.height = '';
}
private getIframeDom(): HTMLIFrameElement {
const iframe = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId).querySelector('iframe');
if (!iframe) throw new Error('Could not find iframe!');
return iframe;
}
public loadCoWebsite(url: string, base: string, allowPolicy?: string): void {
this.load();
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
<img src="resources/logos/close.svg">
</button>`;
setTimeout(() => {
HtmlUtils.getElementByIdOrFail('cowebsite-close').addEventListener('click', () => {
this.closeCoWebsite();
});
}, 100);
this.cowebsiteMainDom.innerHTML = ``;
const iframe = document.createElement('iframe');
iframe.id = 'cowebsite-iframe';
iframe.src = url;
iframe.src = (new URL(url, base)).toString();
if (allowPolicy) {
iframe.allow = allowPolicy;
}
const onloadPromise = new Promise((resolve) => {
iframe.onload = () => resolve();
});
this.cowebsiteDiv.appendChild(iframe);
this.cowebsiteMainDom.appendChild(iframe);
const onTimeoutPromise = new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
});
@ -79,7 +150,8 @@ class CoWebsiteManager {
*/
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
this.load();
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => {
this.cowebsiteMainDom.innerHTML = ``;
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteMainDom)).then(() => {
this.open();
setTimeout(() => {
this.fire();
@ -93,9 +165,7 @@ class CoWebsiteManager {
this.close();
this.fire();
setTimeout(() => {
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
<img src="resources/logos/close.svg">
</button>`;
this.cowebsiteMainDom.innerHTML = ``;
resolve();
}, animationTime)
}));
@ -109,29 +179,36 @@ class CoWebsiteManager {
height: window.innerHeight
}
}
if (window.innerWidth >= window.innerHeight) {
if (!this.verticalMode) {
return {
width: window.innerWidth / 2,
width: window.innerWidth - this.width,
height: window.innerHeight
}
} else {
return {
width: window.innerWidth,
height: window.innerHeight / 2
height: window.innerHeight - this.height,
}
}
}
//todo: is it still useful to allow any kind of observers?
public onStateChange(observer: CoWebsiteStateChangedCallback) {
this.observers.push(observer);
}
private fire(): void {
for (const callback of this.observers) {
callback();
this._onResize.next();
}
private fullscreen(): void {
if (this.isFullScreen) {
this.resetStyle();
//we don't trigger a resize of the phaser game since it won't be visible anyway.
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
} else {
this.verticalMode ? this.height = window.innerHeight : this.width = window.innerWidth;
//we don't trigger a resize of the phaser game since it won't be visible anyway.
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'none';
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'inline';
}
}
}
export const coWebsiteManager = new CoWebsiteManager();
export const coWebsiteManager = new CoWebsiteManager();

View File

@ -192,7 +192,7 @@ class LayoutManager {
} else {
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('sidebar').style.display = 'none';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-section').style.display = 'none';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'flex';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'grid';
}
for (const div of this.importantDivs.values()) {

View File

@ -1,17 +1,18 @@
import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig;
import "../dist/resources/style/index.scss";
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
import {ResizableScene} from "./Phaser/Login/ResizableScene";
import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
@ -71,7 +72,7 @@ const config: GameConfig = {
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
zoom: RESOLUTION,
fps: fps,
dom: {
@ -102,6 +103,7 @@ const config: GameConfig = {
const game = new Phaser.Game(config);
window.addEventListener('resize', function (event) {
coWebsiteManager.resetStyle();
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
@ -113,7 +115,7 @@ window.addEventListener('resize', function (event) {
}
});
coWebsiteManager.onStateChange(() => {
coWebsiteManager.onResize.subscribe(() => {
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
});

View File

@ -2,7 +2,7 @@
set -x
set -o nounset errexit
template_file_index=dist/index.tmpl.html
generated_file_index=dist/index.html
generated_file_index=dist/index.tmpl.html.tmp
tmp_trackcodefile=/tmp/trackcode
# To inject tracking code, you have two choices:

View File

@ -1,6 +1,7 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.ts',
@ -23,6 +24,10 @@ module.exports = {
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
},
],
},
resolve: {
@ -37,9 +42,10 @@ module.exports = {
require('webpack-require-http')
],
plugins: [
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.tmpl.html',
template: './dist/index.tmpl.html.tmp',
minify: {
collapseWhitespace: true,
keepClosingSlash: true,

View File

@ -640,10 +640,10 @@ bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
version "4.11.9"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.1.1:
version "5.1.3"
@ -714,7 +714,7 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
brorand@^1.0.1:
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@ -878,6 +878,11 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -905,6 +910,21 @@ charenc@0.0.2:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
"chokidar@>=2.0.0 <4.0.0":
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.3.1"
chokidar@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@ -1034,6 +1054,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@ -1222,6 +1247,24 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
css-loader@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.3.tgz#87f6fc96816b20debe3cf682f85c7e56a963d0d1"
integrity sha512-CoPZvyh8sLiGARK3gqczpfdedbM74klGWurF2CsNZ2lhNaXdLIUks+3Mfax3WBeRuHoglU+m7KG/+7gY6G4aag==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
icss-utils "^5.1.0"
loader-utils "^2.0.0"
postcss "^8.2.8"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
schema-utils "^3.0.0"
semver "^7.3.4"
css-select@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
@ -1237,6 +1280,11 @@ css-what@2.1:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@ -1493,17 +1541,17 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
elliptic@^6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
dependencies:
bn.js "^4.4.0"
brorand "^1.0.1"
bn.js "^4.11.9"
brorand "^1.1.0"
hash.js "^1.0.0"
hmac-drbg "^1.0.0"
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
hmac-drbg "^1.0.1"
inherits "^2.0.4"
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
emoji-regex@^7.0.1:
version "7.0.3"
@ -2128,6 +2176,11 @@ fsevents@~2.1.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
fsevents@~2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -2353,7 +2406,7 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hmac-drbg@^1.0.0:
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@ -2497,6 +2550,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
ieee754@^1.1.4:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@ -2543,6 +2601,11 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
@ -2942,6 +3005,11 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klona@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@ -3010,6 +3078,13 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@ -3156,12 +3231,21 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mini-css-extract-plugin@^1.3.9:
version "1.3.9"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.9.tgz#47a32132b0fd97a119acd530e8421e8f6ab16d5e"
integrity sha512-Ac4s+xhVbqlyhXS5J/Vh/QXUz3ycXlCqoCPpg0vdfhsIBH9eg/It/9L1r1XhSCH737M1lqcWnMuWL13zcygn5A==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
webpack-sources "^1.1.0"
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
@ -3259,6 +3343,11 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@^3.1.20:
version "3.1.22"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -3286,7 +3375,7 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
neo-async@^2.5.0, neo-async@^2.6.1:
neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@ -3721,6 +3810,58 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.1.0"
postcss-modules-scope@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
dependencies:
cssesc "^3.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"
util-deprecate "^1.0.2"
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@^8.2.8:
version "8.2.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.20"
source-map "^0.6.1"
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@ -3940,6 +4081,13 @@ readdirp@~3.4.0:
dependencies:
picomatch "^2.2.1"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@ -4123,6 +4271,24 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass-loader@10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.1.1.tgz#4ddd5a3d7638e7949065dd6e9c7c04037f7e663d"
integrity sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw==
dependencies:
klona "^2.0.4"
loader-utils "^2.0.0"
neo-async "^2.6.2"
schema-utils "^3.0.0"
semver "^7.3.2"
sass@^1.32.8:
version "1.32.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
dependencies:
chokidar ">=2.0.0 <4.0.0"
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@ -4168,6 +4334,13 @@ semver@^7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
semver@^7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
dependencies:
lru-cache "^6.0.0"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@ -4812,6 +4985,11 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^2.0.1"
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
unique-filename@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
@ -4877,7 +5055,7 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@ -5056,7 +5234,7 @@ webpack-require-http@^0.4.3:
md5 "^2.0.0"
url "^0.11.0"
webpack-sources@^1.4.0, webpack-sources@^1.4.1:
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
@ -5193,6 +5371,11 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"

View File

@ -217,6 +217,7 @@ message ServerToClientMessage {
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13;
AdminRoomMessage adminRoomMessage = 14;
}
}
@ -352,6 +353,12 @@ message AdminMessage {
string type = 4;
}
// A message sent by an administrator to everyone in a specific room
message AdminRoomMessage {
string message = 1;
string roomId = 2;
}
// A message sent by an administrator to absolutely everybody
message AdminGlobalMessage {
string message = 1;
@ -375,4 +382,5 @@ service RoomManager {
rpc sendAdminMessage(AdminMessage) returns (EmptyMessage);
rpc sendGlobalAdminMessage(AdminGlobalMessage) returns (EmptyMessage);
rpc ban(BanMessage) returns (EmptyMessage);
rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage);
}

View File

@ -5,6 +5,7 @@ import {MapController} from "./Controller/MapController";
import {PrometheusController} from "./Controller/PrometheusController";
import {DebugController} from "./Controller/DebugController";
import {App as uwsApp} from "./Server/sifrr.server";
import {AdminController} from "./Controller/AdminController";
class App {
public app: uwsApp;
@ -13,6 +14,7 @@ class App {
public mapController: MapController;
public prometheusController: PrometheusController;
private debugController: DebugController;
private adminController: AdminController;
constructor() {
this.app = new uwsApp();
@ -23,6 +25,7 @@ class App {
this.mapController = new MapController(this.app);
this.prometheusController = new PrometheusController(this.app);
this.debugController = new DebugController(this.app);
this.adminController = new AdminController(this.app);
}
}

View File

@ -0,0 +1,73 @@
import {BaseController} from "./BaseController";
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
import {apiClientRepository} from "../Services/ApiClientRepository";
import {AdminRoomMessage} from "../Messages/generated/messages_pb";
export class AdminController extends BaseController{
constructor(private App : TemplatedApp) {
super();
this.App = App;
this.receiveGlobalMessagePrompt();
}
receiveGlobalMessagePrompt() {
this.App.options("/message", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => {
res.onAborted(() => {
console.warn('/message request was aborted');
})
const token = req.getHeader('admin-token');
const body = await res.json();
if (token !== ADMIN_API_TOKEN) {
console.error('Admin access refused for token: '+token)
res.writeStatus("401 Unauthorized").end('Incorrect token');
return;
}
try {
if (typeof body.text !== 'string') {
throw 'Incorrect text parameter'
}
if (!body.targets || typeof body.targets !== 'object') {
throw 'Incorrect targets parameter'
}
const text: string = body.text;
const targets: string[] = body.targets;
await Promise.all(targets.map((roomId) => {
return apiClientRepository.getClient(roomId).then((roomClient) =>{
return new Promise((res, rej) => {
const roomMessage = new AdminRoomMessage();
roomMessage.setMessage(text);
roomMessage.setRoomid(roomId);
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
err ? rej(err) : res();
});
});
});
}));
} catch (err) {
this.errorToResponse(err, res);
return;
}
res.writeStatus("200");
this.addCorsHeaders(res);
res.end('ok');
});
}
}

View File

@ -56,6 +56,7 @@ export class AuthenticateController extends BaseController {
worldSlug,
roomSlug,
mapUrlStart,
organizationMemberToken,
textures
}));

View File

@ -1,5 +1,5 @@
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import {GameRoomPolicyTypes} from "../Model/PusherRoom";
import {GameRoomPolicyTypes, PusherRoom} from "../Model/PusherRoom";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {
SetPlayerDetailsMessage,
@ -20,11 +20,11 @@ import {parse} from "query-string";
import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {SocketManager, socketManager} from "../Services/SocketManager";
import {emitInBatch} from "../Services/IoSocketHelpers";
import {clientEventsEmitter} from "../Services/ClientEventsEmitter";
import {emitError, emitInBatch} from "../Services/IoSocketHelpers";
import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable";
import {Zone} from "_Model/Zone";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
import {v4} from "uuid";
export class IoSocketController {
private nextUserId: number = 1;
@ -64,22 +64,6 @@ export class IoSocketController {
ws.disconnecting = false;
socketManager.handleAdminRoom(ws as ExAdminSocketInterface, ws.roomId as string);
/*ws.send('Data:'+JSON.stringify(socketManager.getAdminSocketDataFor(ws.roomId as string)));
ws.clientJoinCallback = (clientUUid: string, roomId: string) => {
const wsroomId = ws.roomId as string;
if(wsroomId === roomId) {
ws.send('MemberJoin:'+clientUUid+';'+roomId);
}
};
ws.clientLeaveCallback = (clientUUid: string, roomId: string) => {
const wsroomId = ws.roomId as string;
if(wsroomId === roomId) {
ws.send('MemberLeave:'+clientUUid+';'+roomId);
}
};
clientEventsEmitter.registerToClientJoin(ws.clientJoinCallback);
clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback);*/
},
message: (ws, arrayBuffer, isBinary): void => {
try {
@ -106,7 +90,6 @@ export class IoSocketController {
const Client = (ws as ExAdminSocketInterface);
try {
Client.disconnecting = true;
//leave room
socketManager.leaveAdminRoom(Client);
} catch (e) {
console.error('An error occurred on admin "disconnect"');
@ -125,7 +108,6 @@ export class IoSocketController {
maxBackpressure: 65536, // Maximum 64kB of data in the buffer.
//idleTimeout: 10,
upgrade: (res, req, context) => {
//console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!');
(async () => {
/* Keep track of abortions */
const upgradeAborted = {aborted: false};
@ -173,22 +155,47 @@ export class IoSocketController {
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = [];
let memberMessages: unknown;
let memberTextures: CharacterTexture[] = [];
const room = await socketManager.getOrCreateRoom(roomId);
// TODO: make sure the room isFull is ported in the back part.
/*if(room.isFull){
throw new Error('Room is full');
}*/
if (ADMIN_API_URL) {
try {
const userData = await adminApi.fetchMemberDataByUuid(userUuid);
//console.log('USERDATA', userData)
let userData : FetchMemberDataByUuidResponse = {
uuid: v4(),
tags: [],
textures: [],
messages: [],
anonymous: true
};
try {
userData = await adminApi.fetchMemberDataByUuid(userUuid, roomId);
}catch (err){
if (err?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.');
} else if(err?.response?.status == 403) {
// If we get an HTTP 404, the world is full. We need to broadcast a special error to the client.
// we finish immediatly the upgrade then we will close the socket as soon as it starts opening.
res.upgrade({
rejected: true,
}, websocketKey,
websocketProtocol,
websocketExtensions,
context);
return;
}else{
throw err;
}
}
memberMessages = userData.messages;
memberTags = userData.tags;
memberTextures = userData.textures;
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) {
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
throw new Error('No correct tags')
}
//console.log('access granted for user '+userUuid+' and room '+roomId);
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
throw new Error('No correct member')
}
} catch (e) {
console.log('access not granted for user '+userUuid+' and room '+roomId);
console.error(e);
@ -215,6 +222,7 @@ export class IoSocketController {
roomId,
name,
characterLayers: characterLayerObjs,
messages: memberMessages,
tags: memberTags,
textures: memberTextures,
position: {
@ -241,7 +249,6 @@ export class IoSocketController {
console.log(e.message);
res.writeStatus("401 Unauthorized").end(e.message);
} else {
console.log(e);
res.writeStatus("500 Internal Server Error").end('An error occurred');
}
return;
@ -250,32 +257,30 @@ export class IoSocketController {
},
/* Handlers */
open: (ws) => {
if(ws.rejected === true) {
emitError(ws, 'World is full');
ws.close();
}
// Let's join the room
const client = this.initClient(ws); //todo: into the upgrade instead?
socketManager.handleJoinRoom(client);
//get data information and show messages
if (ADMIN_API_URL) {
adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => {
if (!res.messages) {
return;
if (client.messages && Array.isArray(client.messages)) {
client.messages.forEach((c: unknown) => {
const messageToSend = c as { type: string, message: string };
const sendUserMessage = new SendUserMessage();
sendUserMessage.setType(messageToSend.type);
sendUserMessage.setMessage(messageToSend.message);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
res.messages.forEach((c: unknown) => {
const messageToSend = c as { type: string, message: string };
const sendUserMessage = new SendUserMessage();
sendUserMessage.setType(messageToSend.type);
sendUserMessage.setMessage(messageToSend.message);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
});
}).catch((err) => {
console.error('fetchMemberDataByUuid => err', err);
});
}
},
@ -340,6 +345,7 @@ export class IoSocketController {
}
client.disconnecting = false;
client.messages = ws.messages;
client.name = ws.name;
client.tags = ws.tags;
client.textures = ws.textures;

View File

@ -36,6 +36,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
batchedMessages: BatchMessage;
batchTimeout: NodeJS.Timeout|null;
disconnecting: boolean,
messages: unknown,
tags: string[],
textures: CharacterTexture[],
backConnection: BackConnection,

View File

@ -1,6 +1,5 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios";
import {v4} from "uuid";
export interface AdminApiData {
organizationSlug: string
@ -31,6 +30,7 @@ export interface FetchMemberDataByUuidResponse {
tags: string[];
textures: CharacterTexture[];
messages: unknown[];
anonymous?: boolean;
}
class AdminApi {
@ -58,29 +58,14 @@ class AdminApi {
return res.data;
}
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
try {
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
} catch (e) {
if (e?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
return {
uuid: v4(),
tags: [],
textures: [],
messages: [],
}
} else {
throw e;
}
}
const res = await Axios.get(ADMIN_API_URL+'/api/room/access',
{ params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {

View File

@ -13,8 +13,7 @@ const debug = Debug('apiClientRespository');
class ApiClientRepository {
private roomManagerClients: RoomManagerClient[] = [];
public constructor(private apiUrls: string[]) {
}
public constructor(private apiUrls: string[]) {}
public async getClient(roomId: string): Promise<RoomManagerClient> {
const array = new Uint32Array(crypto.createHash('md5').update(roomId).digest());

View File

@ -1,5 +1,6 @@
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
import {WebSocket} from "uWebSockets.js";
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
socket.batchedMessages.addPayload(payload);
@ -20,7 +21,7 @@ export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): voi
}
}
export function emitError(Client: ExSocketInterface, message: string): void {
export function emitError(Client: WebSocket, message: string): void {
const errorMessage = new ErrorMessage();
errorMessage.setMessage(message);

View File

@ -4,7 +4,6 @@ import {
GroupDeleteMessage,
ItemEventMessage,
PlayGlobalMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
SetPlayerDetailsMessage,
@ -25,21 +24,15 @@ import {
SendUserMessage,
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {cpuTracker} from "./CpuTracker";
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {adminApi, CharacterTexture} from "./AdminApi";
import Direction = PositionMessage.Direction;
import {emitError, emitInBatch} from "./IoSocketHelpers";
import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager";
import {apiClientRepository} from "./ApiClientRepository";
import {ServiceError} from "grpc";
import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone";
import Debug from "debug";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
@ -271,31 +264,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setItemeventmessage(itemEventMessage);
client.backConnection.write(pusherToBackMessage);
/*const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
try {
const world = this.Worlds.get(ws.roomId);
if (!world) {
console.error("Could not find world with id '", ws.roomId, "'");
return;
}
const subMessage = new SubMessage();
subMessage.setItemeventmessage(itemEventMessage);
// Let's send the event without using the SocketIO room.
for (const user of world.getUsers().values()) {
const client = this.searchClientByIdOrFail(user.id);
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
emitInBatch(client, subMessage);
}
world.setItemState(itemEvent.itemId, itemEvent.state);
} catch (e) {
console.error('An error occurred on "item_event"');
console.error(e);
}*/
}
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
@ -317,25 +285,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setWebrtcsignaltoservermessage(data);
socket.backConnection.write(pusherToBackMessage);
//send only at user
/*const client = this.sockets.get(data.getReceiverid());
if (client === undefined) {
console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(socket.userId);
webrtcSignalToClient.setSignal(data.getSignal());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
}
emitScreenSharing(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void {
@ -343,24 +292,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setWebrtcscreensharingsignaltoservermessage(data);
socket.backConnection.write(pusherToBackMessage);
//send only at user
/*const client = this.sockets.get(data.getReceiverid());
if (client === undefined) {
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(socket.userId);
webrtcSignalToClient.setSignal(data.getSignal());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
}
private searchClientByIdOrFail(userId: number): ExSocketInterface {
@ -408,17 +339,7 @@ export class SocketManager implements ZoneEventListener {
//check and create new world for a room
let world = this.Worlds.get(roomId)
if(world === undefined){
world = new PusherRoom(
roomId,
this
/* (user: User, group: Group) => this.joinWebRtcRoom(user, group),
(user: User, group: Group) => this.disConnectedUser(user, group),
MINIMUM_DISTANCE,
GROUP_RADIUS,
(thing: Movable, listener: User) => this.onRoomEnter(thing, listener),
(thing: Movable, position:PositionInterface, listener:User) => this.onClientMove(thing, position, listener),
(thing: Movable, listener:User) => this.onClientLeave(thing, listener)*/
);
world = new PusherRoom(roomId, this);
if (!world.anonymous) {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
world.tags = data.tags
@ -429,60 +350,6 @@ export class SocketManager implements ZoneEventListener {
return Promise.resolve(world)
}
/* private joinRoom(client : ExSocketInterface, position: PointInterface): PusherRoom {
const roomId = client.roomId;
client.position = position;
const world = this.Worlds.get(roomId)
if(world === undefined){
throw new Error('Could not find room for ID: '+client.roomId)
}
// Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(client, group);
});
//join world
world.join(client, client.position);
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
return world;
}
private onClientMove(thing: Movable, position:PositionInterface, listener:User): void {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(clientUser.userId);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}
private onClientLeave(thing: Movable, listener:User) {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
this.emitUserLeftEvent(clientListener, clientUser.userId);
} else if (thing instanceof Group) {
this.emitDeleteGroupEvent(clientListener, thing.getId());
} else {
console.error('Unexpected type for Movable.');
}
}*/
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
@ -493,11 +360,7 @@ export class SocketManager implements ZoneEventListener {
public getWorlds(): Map<string, PusherRoom> {
return this.Worlds;
}
/**
*
* @param token
*/
searchClientByUuid(uuid: string): ExSocketInterface | null {
for(const socket of this.sockets.values()){
if(socket.userUuid === uuid){
@ -550,7 +413,7 @@ export class SocketManager implements ZoneEventListener {
}
public async emitSendUserMessage(userUuid: string, message: string, type: string, roomId: string) {
const client = this.searchClientByUuid(userUuid);
/*const client = this.searchClientByUuid(userUuid);
if(client) {
const adminMessage = new SendUserMessage();
adminMessage.setMessage(message);
@ -559,7 +422,7 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setSendusermessage(adminMessage);
client.backConnection.write(pusherToBackMessage);
return;
}
}*/
const backConnection = await apiClientRepository.getClient(roomId);
const backAdminMessage = new AdminMessage();
@ -575,7 +438,7 @@ export class SocketManager implements ZoneEventListener {
}
public async emitBan(userUuid: string, message: string, type: string, roomId: string) {
const client = this.searchClientByUuid(userUuid);
/*const client = this.searchClientByUuid(userUuid);
if(client) {
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
@ -584,7 +447,7 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
return;
}
}*/
const backConnection = await apiClientRepository.getClient(roomId);
const banMessage = new BanMessage();