Merge branch 'develop' into FTUEPopup
This commit is contained in:
commit
d80681c40b
@ -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=
|
||||
|
@ -1,4 +1,4 @@
|
||||
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg)
|
||||
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
|
||||
|
||||
![WorkAdventure landscape image](README-INTRO.jpg)
|
||||
|
||||
@ -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
|
||||
|
@ -1,9 +1,3 @@
|
||||
import { Group } from "./Group";
|
||||
import { PointInterface } from "./Websocket/PointInterface";
|
||||
import {Zone} from "_Model/Zone";
|
||||
import {Movable} from "_Model/Movable";
|
||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
||||
import {ServerDuplexStream} from "grpc";
|
||||
import {
|
||||
BatchMessage,
|
||||
PusherToBackMessage,
|
||||
@ -11,7 +5,6 @@ import {
|
||||
ServerToClientMessage,
|
||||
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
||||
import {AdminSocket} from "../RoomManager";
|
||||
|
||||
|
||||
|
@ -38,12 +38,10 @@ export class GameRoom {
|
||||
|
||||
private readonly positionNotifier: PositionNotifier;
|
||||
public readonly roomId: string;
|
||||
public readonly anonymous: boolean;
|
||||
public tags: string[];
|
||||
public policyType: GameRoomPolicyTypes;
|
||||
public readonly roomSlug: string;
|
||||
public readonly worldSlug: string = '';
|
||||
public readonly organizationSlug: string = '';
|
||||
private versionNumber:number = 1;
|
||||
private nextUserId: number = 1;
|
||||
|
||||
constructor(roomId: string,
|
||||
@ -56,11 +54,8 @@ export class GameRoom {
|
||||
onLeaves: LeavesCallback)
|
||||
{
|
||||
this.roomId = roomId;
|
||||
this.anonymous = isRoomAnonymous(roomId);
|
||||
this.tags = [];
|
||||
this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY;
|
||||
|
||||
if (this.anonymous) {
|
||||
if (isRoomAnonymous(roomId)) {
|
||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||
} else {
|
||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
||||
@ -304,10 +299,6 @@ export class GameRoom {
|
||||
return this.itemsState;
|
||||
}
|
||||
|
||||
public canAccess(userTags: string[]): boolean {
|
||||
return arrayIntersect(userTags, this.tags);
|
||||
}
|
||||
|
||||
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
||||
return this.positionNotifier.addZoneListener(call, x, y);
|
||||
}
|
||||
@ -328,4 +319,9 @@ export class GameRoom {
|
||||
public adminLeave(admin: Admin): void {
|
||||
this.admins.delete(admin);
|
||||
}
|
||||
|
||||
public incrementVersion(): number {
|
||||
this.versionNumber++
|
||||
return this.versionNumber;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
|
||||
import {
|
||||
AdminGlobalMessage,
|
||||
AdminMessage,
|
||||
AdminPusherToBackMessage,
|
||||
AdminPusherToBackMessage,
|
||||
AdminRoomMessage,
|
||||
BanMessage,
|
||||
EmptyMessage,
|
||||
@ -10,12 +10,12 @@ import {
|
||||
JoinRoomMessage,
|
||||
PlayGlobalMessage,
|
||||
PusherToBackMessage,
|
||||
QueryJitsiJwtMessage,
|
||||
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
|
||||
ServerToAdminClientMessage,
|
||||
ServerToClientMessage,
|
||||
SilentMessage,
|
||||
UserMovesMessage,
|
||||
WebRtcSignalToServerMessage,
|
||||
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
|
||||
ZoneMessage
|
||||
} from "./Messages/generated/messages_pb";
|
||||
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
||||
@ -43,8 +43,13 @@ const roomManager: IRoomManagerServer = {
|
||||
if (room === null || user === null) {
|
||||
if (message.hasJoinroommessage()) {
|
||||
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
||||
room = gameRoom;
|
||||
user = myUser;
|
||||
if (call.writable) {
|
||||
room = gameRoom;
|
||||
user = myUser;
|
||||
} else {
|
||||
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
||||
socketManager.leaveRoom(gameRoom, myUser);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
||||
@ -184,6 +189,14 @@ const roomManager: IRoomManagerServer = {
|
||||
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
||||
callback(null, new EmptyMessage());
|
||||
},
|
||||
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
||||
callback(null, new EmptyMessage());
|
||||
},
|
||||
sendRefreshRoomPrompt(call: ServerUnaryCall<RefreshRoomPromptMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
||||
callback(null, new EmptyMessage());
|
||||
},
|
||||
};
|
||||
|
||||
export {roomManager};
|
||||
|
@ -1,49 +0,0 @@
|
||||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
|
||||
export interface AdminApiData {
|
||||
organizationSlug: string
|
||||
worldSlug: string
|
||||
roomSlug: string
|
||||
mapUrlStart: string
|
||||
tags: string[]
|
||||
policy_type: number
|
||||
userUuid: string
|
||||
messages?: unknown[],
|
||||
textures: CharacterTexture[]
|
||||
}
|
||||
|
||||
export interface CharacterTexture {
|
||||
id: number,
|
||||
level: number,
|
||||
url: string,
|
||||
rights: string
|
||||
}
|
||||
|
||||
class AdminApi {
|
||||
|
||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
}
|
||||
|
||||
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
||||
organizationSlug,
|
||||
worldSlug
|
||||
};
|
||||
|
||||
if (roomSlug) {
|
||||
params.roomSlug = roomSlug;
|
||||
}
|
||||
|
||||
const res = await Axios.get(ADMIN_API_URL + '/api/map',
|
||||
{
|
||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
|
||||
params
|
||||
}
|
||||
)
|
||||
return res.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
@ -24,8 +24,9 @@ import {
|
||||
UserJoinedZoneMessage,
|
||||
GroupUpdateZoneMessage,
|
||||
GroupLeftZoneMessage,
|
||||
WorldFullWarningMessage,
|
||||
UserLeftZoneMessage,
|
||||
BanUserMessage,
|
||||
BanUserMessage, RefreshRoomMessage,
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {User, UserSocket} from "../Model/User";
|
||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||
@ -40,7 +41,6 @@ import {
|
||||
} from "../Enum/EnvironmentVariable";
|
||||
import {Movable} from "../Model/Movable";
|
||||
import {PositionInterface} from "../Model/PositionInterface";
|
||||
import {adminApi, CharacterTexture} from "./AdminApi";
|
||||
import Jwt from "jsonwebtoken";
|
||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
||||
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
||||
@ -58,6 +58,7 @@ function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): vo
|
||||
// TODO: should we batch those every 100ms?
|
||||
const batchMessage = new BatchToPusherMessage();
|
||||
batchMessage.addPayload(subMessage);
|
||||
|
||||
|
||||
socket.write(batchMessage);
|
||||
}
|
||||
@ -75,10 +76,17 @@ export class SocketManager {
|
||||
}
|
||||
|
||||
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
|
||||
|
||||
|
||||
//join new previous room
|
||||
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
||||
|
||||
|
||||
if (!socket.writable) {
|
||||
console.warn('Socket was aborted');
|
||||
return {
|
||||
room,
|
||||
user
|
||||
};
|
||||
}
|
||||
const roomJoinedMessage = new RoomJoinedMessage();
|
||||
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
||||
|
||||
@ -94,7 +102,6 @@ export class SocketManager {
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
||||
console.log('SENDING MESSAGE roomJoinedMessage');
|
||||
socket.write(serverToClientMessage);
|
||||
|
||||
return {
|
||||
@ -121,15 +128,7 @@ export class SocketManager {
|
||||
if (viewport === undefined) {
|
||||
throw new Error('Viewport not found in message');
|
||||
}
|
||||
|
||||
// sending to all clients in room except sender
|
||||
/*client.position = {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
direction,
|
||||
moving: position.moving,
|
||||
};
|
||||
client.viewport = viewport;*/
|
||||
|
||||
|
||||
// update position in the world
|
||||
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
||||
@ -184,21 +183,6 @@ export class SocketManager {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle this message in pusher
|
||||
/*async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
||||
try {
|
||||
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
||||
if (!reportedSocket) {
|
||||
throw 'reported socket user not found';
|
||||
}
|
||||
//TODO report user on admin application
|
||||
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
|
||||
} catch (e) {
|
||||
console.error('An error occurred on "handleReportMessage"');
|
||||
console.error(e);
|
||||
}
|
||||
}*/
|
||||
|
||||
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
|
||||
//send only at user
|
||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||
@ -262,8 +246,6 @@ export class SocketManager {
|
||||
debug('Room is empty. Deleting room "%s"', room.roomId);
|
||||
}
|
||||
} finally {
|
||||
//delete Client.roomId;
|
||||
//this.sockets.delete(Client.userId);
|
||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
||||
console.log('A user left');
|
||||
}
|
||||
@ -283,11 +265,6 @@ export class SocketManager {
|
||||
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
||||
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
|
||||
);
|
||||
if (!world.anonymous) {
|
||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
||||
world.tags = data.tags
|
||||
world.policyType = Number(data.policy_type)
|
||||
}
|
||||
gaugeManager.incNbRoomGauge();
|
||||
this.rooms.set(roomId, world);
|
||||
}
|
||||
@ -298,20 +275,14 @@ export class SocketManager {
|
||||
|
||||
const roomId = joinRoomMessage.getRoomid();
|
||||
|
||||
const world = await socketManager.getOrCreateRoom(roomId);
|
||||
|
||||
// Dispatch groups position to newly connected user
|
||||
/*world.getGroups().forEach((group: Group) => {
|
||||
this.emitCreateUpdateGroupEvent(socket, group);
|
||||
});*/
|
||||
const room = await socketManager.getOrCreateRoom(roomId);
|
||||
|
||||
//join world
|
||||
const user = world.join(socket, joinRoomMessage);
|
||||
const user = room.join(socket, joinRoomMessage);
|
||||
|
||||
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
||||
//console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
|
||||
console.log(new Date().toISOString() + ' A user joined');
|
||||
return {room: world, user};
|
||||
return {room, user};
|
||||
}
|
||||
|
||||
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
||||
@ -419,10 +390,6 @@ export class SocketManager {
|
||||
}
|
||||
|
||||
private joinWebRtcRoom(user: User, group: Group) {
|
||||
/*const roomId: string = "webrtcroom"+group.getId();
|
||||
if (user.socket.webRtcRoomId === roomId) {
|
||||
return;
|
||||
}*/
|
||||
|
||||
for (const otherUser of group.getUsers()) {
|
||||
if (user === otherUser) {
|
||||
@ -758,6 +725,43 @@ export class SocketManager {
|
||||
recipient.socket.write(clientMessage);
|
||||
});
|
||||
}
|
||||
|
||||
dispatchWorlFullWarning(roomId: string,): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
//todo: this should cause the http call to return a 500
|
||||
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||
return;
|
||||
}
|
||||
|
||||
room.getUsers().forEach((recipient) => {
|
||||
const worldFullMessage = new WorldFullWarningMessage();
|
||||
|
||||
const clientMessage = new ServerToClientMessage();
|
||||
clientMessage.setWorldfullwarningmessage(worldFullMessage);
|
||||
|
||||
recipient.socket.write(clientMessage);
|
||||
});
|
||||
}
|
||||
|
||||
dispatchRoomRefresh(roomId: string,): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
const versionNumber = room.incrementVersion();
|
||||
room.getUsers().forEach((recipient) => {
|
||||
const worldFullMessage = new RefreshRoomMessage();
|
||||
worldFullMessage.setRoomid(roomId)
|
||||
worldFullMessage.setVersionnumber(versionNumber)
|
||||
|
||||
const clientMessage = new ServerToClientMessage();
|
||||
clientMessage.setRefreshroommessage(worldFullMessage);
|
||||
|
||||
recipient.socket.write(clientMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const socketManager = new SocketManager();
|
||||
|
@ -3032,9 +3032,9 @@ xtend@^4.0.0:
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||
|
||||
yallist@^3.0.0, yallist@^3.0.3:
|
||||
version "3.1.1"
|
||||
|
@ -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
|
||||
@ -37,7 +37,7 @@ services:
|
||||
DEBUG_MODE: "$DEBUG_MODE"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||
API_URL: pusher.${DOMAIN}
|
||||
PUSHER_URL: //pusher.${DOMAIN}
|
||||
TURN_SERVER: "${TURN_SERVER}"
|
||||
TURN_USER: "${TURN_USER}"
|
||||
TURN_PASSWORD: "${TURN_PASSWORD}"
|
||||
|
@ -82,9 +82,9 @@
|
||||
},
|
||||
"ports": [80],
|
||||
"env": {
|
||||
"API_URL": "pusher."+url,
|
||||
"UPLOADER_URL": "uploader."+url,
|
||||
"ADMIN_URL": url,
|
||||
"PUSHER_URL": "//pusher."+url,
|
||||
"UPLOADER_URL": "//uploader."+url,
|
||||
"ADMIN_URL": "//"+url,
|
||||
"JITSI_URL": env.JITSI_URL,
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
||||
|
207
docker-compose.single-domain.yaml
Normal file
207
docker-compose.single-domain.yaml
Normal file
@ -0,0 +1,207 @@
|
||||
version: "3"
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik:v2.0
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --providers.docker
|
||||
- --entryPoints.web.address=:80
|
||||
- --entryPoints.websecure.address=:443
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# The Web UI (enabled by --api.insecure=true)
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- back
|
||||
- front
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
front:
|
||||
image: thecodingmachine/nodejs:14
|
||||
environment:
|
||||
DEBUG_MODE: "$DEBUG_MODE"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||
HOST: "0.0.0.0"
|
||||
NODE_ENV: development
|
||||
PUSHER_URL: /pusher
|
||||
UPLOADER_URL: /uploader
|
||||
ADMIN_URL: /admin
|
||||
MAPS_URL: /maps
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||
TURN_USER: ""
|
||||
TURN_PASSWORD: ""
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
labels:
|
||||
- "traefik.http.routers.front.rule=PathPrefix(`/`)"
|
||||
- "traefik.http.routers.front.entryPoints=web,traefik"
|
||||
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.front-ssl.rule=PathPrefix(`/`)"
|
||||
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.front-ssl.tls=true"
|
||||
- "traefik.http.routers.front-ssl.service=front"
|
||||
|
||||
pusher:
|
||||
image: thecodingmachine/nodejs:12
|
||||
command: yarn dev
|
||||
#command: yarn run prod
|
||||
#command: yarn run profile
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
SECRET_KEY: yourSecretKey
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
API_URL: back:50051
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
volumes:
|
||||
- ./pusher:/usr/src/app
|
||||
labels:
|
||||
- "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher"
|
||||
- "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)"
|
||||
- "traefik.http.routers.pusher.middlewares=strip-pusher-prefix@docker"
|
||||
- "traefik.http.routers.pusher.entryPoints=web"
|
||||
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.pusher-ssl.rule=PathPrefix(`/pusher`)"
|
||||
- "traefik.http.routers.pusher-ssl.middlewares=strip-pusher-prefix@docker"
|
||||
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.pusher-ssl.tls=true"
|
||||
- "traefik.http.routers.pusher-ssl.service=pusher"
|
||||
|
||||
maps:
|
||||
image: thecodingmachine/nodejs:12-apache
|
||||
environment:
|
||||
DEBUG_MODE: "$DEBUG_MODE"
|
||||
HOST: "0.0.0.0"
|
||||
NODE_ENV: development
|
||||
#APACHE_DOCUMENT_ROOT: dist/
|
||||
#APACHE_EXTENSIONS: headers
|
||||
#APACHE_EXTENSION_HEADERS: 1
|
||||
STARTUP_COMMAND_0: sudo a2enmod headers
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
STARTUP_COMMAND_2: yarn run dev &
|
||||
volumes:
|
||||
- ./maps:/var/www/html
|
||||
labels:
|
||||
- "traefik.http.middlewares.strip-maps-prefix.stripprefix.prefixes=/maps"
|
||||
- "traefik.http.routers.maps.rule=PathPrefix(`/maps`)"
|
||||
- "traefik.http.routers.maps.middlewares=strip-maps-prefix@docker"
|
||||
- "traefik.http.routers.maps.entryPoints=web,traefik"
|
||||
- "traefik.http.services.maps.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.maps-ssl.rule=PathPrefix(`/maps`)"
|
||||
- "traefik.http.routers.maps-ssl.middlewares=strip-maps-prefix@docker"
|
||||
- "traefik.http.routers.maps-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.maps-ssl.tls=true"
|
||||
- "traefik.http.routers.maps-ssl.service=maps"
|
||||
|
||||
back:
|
||||
image: thecodingmachine/nodejs:12
|
||||
command: yarn dev
|
||||
#command: yarn run profile
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
SECRET_KEY: yourSecretKey
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ALLOW_ARTILLERY: "true"
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
|
||||
- "traefik.http.routers.back.rule=PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.back.middlewares=strip-api-prefix@docker"
|
||||
- "traefik.http.routers.back.entryPoints=web"
|
||||
- "traefik.http.services.back.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.back-ssl.rule=PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.back-ssl.middlewares=strip-api-prefix@docker"
|
||||
- "traefik.http.routers.back-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.back-ssl.tls=true"
|
||||
- "traefik.http.routers.back-ssl.service=back"
|
||||
|
||||
uploader:
|
||||
image: thecodingmachine/nodejs:12
|
||||
command: yarn dev
|
||||
#command: yarn run profile
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
volumes:
|
||||
- ./uploader:/usr/src/app
|
||||
labels:
|
||||
- "traefik.http.middlewares.strip-uploader-prefix.stripprefix.prefixes=/uploader"
|
||||
- "traefik.http.routers.uploader.rule=PathPrefix(`/uploader`)"
|
||||
- "traefik.http.routers.uploader.middlewares=strip-uploader-prefix@docker"
|
||||
- "traefik.http.routers.uploader.entryPoints=web"
|
||||
- "traefik.http.services.uploader.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.uploader-ssl.rule=PathPrefix(`/uploader`)"
|
||||
- "traefik.http.routers.uploader-ssl.middlewares=strip-uploader-prefix@docker"
|
||||
- "traefik.http.routers.uploader-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.uploader-ssl.tls=true"
|
||||
- "traefik.http.routers.uploader-ssl.service=uploader"
|
||||
|
||||
website:
|
||||
image: thecodingmachine/nodejs:12-apache
|
||||
environment:
|
||||
STARTUP_COMMAND_1: npm install
|
||||
STARTUP_COMMAND_2: npm run watch &
|
||||
APACHE_DOCUMENT_ROOT: dist/
|
||||
volumes:
|
||||
- ./website:/var/www/html
|
||||
labels:
|
||||
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
|
||||
- "traefik.http.routers.website.entryPoints=web"
|
||||
- "traefik.http.services.website.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
|
||||
- "traefik.http.routers.website-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.website-ssl.tls=true"
|
||||
- "traefik.http.routers.website-ssl.service=website"
|
||||
|
||||
messages:
|
||||
#image: thecodingmachine/nodejs:14
|
||||
image: thecodingmachine/workadventure-back-base:latest
|
||||
environment:
|
||||
#STARTUP_COMMAND_0: sudo apt-get install -y inotify-tools
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
STARTUP_COMMAND_2: yarn run proto:watch
|
||||
volumes:
|
||||
- ./messages:/usr/src/app
|
||||
- ./back:/usr/src/back
|
||||
- ./front:/usr/src/front
|
||||
- ./pusher:/usr/src/pusher
|
||||
|
||||
# coturn:
|
||||
# image: coturn/coturn:4.5.2
|
||||
# command:
|
||||
# - turnserver
|
||||
# #- -c=/etc/coturn/turnserver.conf
|
||||
# - --log-file=stdout
|
||||
# - --external-ip=$$(detect-external-ip)
|
||||
# - --listening-port=3478
|
||||
# - --min-port=10000
|
||||
# - --max-port=10010
|
||||
# - --tls-listening-port=5349
|
||||
# - --listening-ip=0.0.0.0
|
||||
# - --realm=localhost
|
||||
# - --server-name=localhost
|
||||
# - --lt-cred-mech
|
||||
# # Enable Coturn "REST API" to validate temporary passwords.
|
||||
# #- --use-auth-secret
|
||||
# #- --static-auth-secret=SomeStaticAuthSecret
|
||||
# #- --userdb=/var/lib/turn/turndb
|
||||
# - --user=workadventure:WorkAdventure123
|
||||
# # use real-valid certificate/privatekey files
|
||||
# #- --cert=/root/letsencrypt/fullchain.pem
|
||||
# #- --pkey=/root/letsencrypt/privkey.pem
|
||||
# network_mode: host
|
@ -26,9 +26,9 @@ services:
|
||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||
HOST: "0.0.0.0"
|
||||
NODE_ENV: development
|
||||
API_URL: pusher.workadventure.localhost
|
||||
UPLOADER_URL: uploader.workadventure.localhost
|
||||
ADMIN_URL: workadventure.localhost
|
||||
PUSHER_URL: //pusher.workadventure.localhost
|
||||
UPLOADER_URL: //uploader.workadventure.localhost
|
||||
ADMIN_URL: //workadventure.localhost
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||
@ -53,10 +53,8 @@ services:
|
||||
pusher:
|
||||
image: thecodingmachine/nodejs:12
|
||||
command: yarn dev
|
||||
#command: yarn run prod
|
||||
#command: yarn run profile
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
DEBUG: "socket:*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
SECRET_KEY: yourSecretKey
|
||||
|
@ -3,7 +3,7 @@ WORKDIR /var/www/messages
|
||||
COPY --chown=docker:docker messages .
|
||||
RUN yarn install && yarn proto
|
||||
|
||||
# we are rebuilding on each deploy to cope with the API_URL environment URL
|
||||
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
|
||||
FROM thecodingmachine/nodejs:14-apache
|
||||
|
||||
COPY --chown=docker:docker front .
|
||||
|
1
front/dist/.gitignore
vendored
1
front/dist/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
index.html
|
||||
index.tmpl.html.tmp
|
||||
/js/
|
||||
style.*.css
|
||||
|
122
front/dist/index.tmpl.html
vendored
122
front/dist/index.tmpl.html
vendored
@ -31,8 +31,6 @@
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/resources/style/style.css">
|
||||
|
||||
|
||||
<title>WorkAdventure</title>
|
||||
</head>
|
||||
@ -70,98 +68,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" alt="fullscreen mode">
|
||||
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
||||
</button>
|
||||
<button class="top-right-btn" id="cowebsite-close" alt="close the iframe">
|
||||
<img src="resources/logos/close.svg"/>
|
||||
</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.025" 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">
|
||||
reduce in conversations
|
||||
<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">
|
||||
|
7
front/dist/resources/html/gameMenu.html
vendored
7
front/dist/resources/html/gameMenu.html
vendored
@ -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;
|
||||
|
7
front/dist/resources/html/gameMenuIcon.html
vendored
7
front/dist/resources/html/gameMenuIcon.html
vendored
@ -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;
|
||||
|
@ -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;
|
||||
|
7
front/dist/resources/html/gameReport.html
vendored
7
front/dist/resources/html/gameReport.html
vendored
@ -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;
|
||||
|
7
front/dist/resources/html/gameShare.html
vendored
7
front/dist/resources/html/gameShare.html
vendored
@ -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;
|
||||
|
103
front/dist/resources/html/helpCameraSettings.html
vendored
Normal file
103
front/dist/resources/html/helpCameraSettings.html
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
<style>
|
||||
#helpCameraSettings {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
width: 400px;
|
||||
height: 370px;
|
||||
}
|
||||
#helpCameraSettings h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#helpCameraSettings input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
}
|
||||
#helpCameraSettings section {
|
||||
margin: 10px;
|
||||
}
|
||||
#helpCameraSettings section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#helpCameraSettings button {
|
||||
margin-top: 10px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#helpCameraSettings section a{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
margin: 0 6px;
|
||||
color: black;
|
||||
}
|
||||
#helpCameraSettings section h6,
|
||||
#helpCameraSettings section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#helpCameraSettings section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#helpCameraSettings section p{
|
||||
font-size: 8px;
|
||||
margin: 0px 20px;
|
||||
}
|
||||
#helpCameraSettings section p.err{
|
||||
color: #ff0000;
|
||||
}
|
||||
#helpCameraSettings section ul{
|
||||
margin: 6px;
|
||||
}
|
||||
#helpCameraSettings section li{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section img {
|
||||
width: 200px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="helpCameraSettings" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Camera/Microphone access needed</h5>
|
||||
<p class="err" id="permissionError">Permission denied</p>
|
||||
<p class="info">You must allow camera and microphone access in your browser.</p>
|
||||
<ul>
|
||||
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
||||
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
||||
</ul>
|
||||
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||
<p id='browserHelpSetting'></p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="helpCameraSettingsFormRefresh">Refresh</button>
|
||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||
</section>
|
||||
</form>
|
18
front/dist/resources/html/warningContainer.html
vendored
Normal file
18
front/dist/resources/html/warningContainer.html
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<style>
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
width: 300px;
|
||||
background-color: red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#warningMain h2 {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main id="warningMain">
|
||||
<h2>Warning!</h2>
|
||||
<p>This world is close to its limit!</p>
|
||||
</main>
|
3
front/dist/resources/logos/fullscreen-exit.svg
vendored
Normal file
3
front/dist/resources/logos/fullscreen-exit.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="i-fullscreen-exit" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M4 12 L12 12 12 4 M20 4 L20 12 28 12 M4 20 L12 20 12 28 M28 20 L20 20 20 28" />
|
||||
</svg>
|
After Width: | Height: | Size: 329 B |
3
front/dist/resources/logos/fullscreen.svg
vendored
Normal file
3
front/dist/resources/logos/fullscreen.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="i-fullscreen" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M4 12 L4 4 12 4 M20 4 L28 4 28 12 M4 20 L4 28 12 28 M28 20 L28 28 20 28" />
|
||||
</svg>
|
After Width: | Height: | Size: 322 B |
BIN
front/dist/resources/objects/help-setting-camera-permission-chrome.png
vendored
Normal file
BIN
front/dist/resources/objects/help-setting-camera-permission-chrome.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
BIN
front/dist/resources/objects/help-setting-camera-permission-firefox.png
vendored
Normal file
BIN
front/dist/resources/objects/help-setting-camera-permission-firefox.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
132
front/dist/resources/style/cowebsite.scss
vendored
Normal file
132
front/dist/resources/style/cowebsite.scss
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/* A potentially shared website could appear in an iframe in the cowebsite space. */
|
||||
|
||||
#cowebsite {
|
||||
position: fixed;
|
||||
transition: transform 0.5s;
|
||||
background-color: white;
|
||||
|
||||
&.loading {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
main {
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
aside {
|
||||
background: gray;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
margin: 3px;
|
||||
pointer-events: none;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-right-btn{
|
||||
position: absolute;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
|
||||
img {
|
||||
height: 25px;
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
img:hover {
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-aspect-ratio: 1/1) {
|
||||
#cowebsite {
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
|
||||
&.loading {
|
||||
transform: translateX(90%);
|
||||
}
|
||||
&.hidden {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
aside {
|
||||
width: 30px;
|
||||
cursor: ew-resize;
|
||||
|
||||
img {
|
||||
cursor: ew-resize;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.top-right-btn{
|
||||
left: -6px;
|
||||
&#cowebsite-close {
|
||||
top: 0px;
|
||||
}
|
||||
&#cowebsite-fullscreen {
|
||||
top: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-aspect-ratio: 1/1) {
|
||||
|
||||
|
||||
#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 {
|
||||
&#cowebsite-close {
|
||||
right: 0px;
|
||||
}
|
||||
&#cowebsite-fullscreen {
|
||||
right: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
front/dist/resources/style/index.scss
vendored
Normal file
2
front/dist/resources/style/index.scss
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
@import "cowebsite.scss";
|
||||
@import "style.css";
|
64
front/dist/resources/style/style.css
vendored
64
front/dist/resources/style/style.css
vendored
@ -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;
|
||||
@ -419,10 +363,14 @@ body {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.audioplayer > div {
|
||||
padding-right: 1.2rem;
|
||||
}
|
||||
|
||||
#audioplayerctrl {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
right: calc(50% - 120px);
|
||||
padding: 0.3rem 0.5rem;
|
||||
color: white;
|
||||
transition: transform 0.5s;
|
||||
|
@ -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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||
import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
|
||||
|
@ -144,6 +144,7 @@ class IframeListener {
|
||||
// production code
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add('allow-scripts');
|
||||
|
@ -1,27 +1,30 @@
|
||||
import Axios from "axios";
|
||||
import {API_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
|
||||
import {PUSHER_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "./RoomConnection";
|
||||
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
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();
|
||||
private reconnectingTimeout: NodeJS.Timeout|null = null;
|
||||
private _unloading:boolean = false;
|
||||
|
||||
get unloading () {
|
||||
return this._unloading;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this._unloading = true;
|
||||
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Tries to login to the node server and return the starting map url to be loaded
|
||||
*/
|
||||
@ -31,7 +34,7 @@ class ConnectionManager {
|
||||
this.connexionType = connexionType;
|
||||
if(connexionType === GameConnexionTypes.register) {
|
||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
||||
const data = await Axios.post(`${PUSHER_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
||||
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
||||
localUserStore.saveUser(this.localUser);
|
||||
|
||||
@ -39,7 +42,7 @@ class ConnectionManager {
|
||||
const worldSlug = data.worldSlug;
|
||||
const roomSlug = data.roomSlug;
|
||||
|
||||
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.hash);
|
||||
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.search + window.location.hash);
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
return Promise.resolve(room);
|
||||
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
||||
@ -61,20 +64,20 @@ class ConnectionManager {
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
roomId = START_ROOM_URL;
|
||||
} else {
|
||||
roomId = window.location.pathname + window.location.hash;
|
||||
roomId = window.location.pathname + window.location.search + window.location.hash;
|
||||
}
|
||||
return Promise.resolve(new Room(roomId));
|
||||
}
|
||||
|
||||
return Promise.reject('Invalid URL');
|
||||
return Promise.reject(new Error('Invalid URL'));
|
||||
}
|
||||
|
||||
private async verifyToken(token: string): Promise<void> {
|
||||
await Axios.get(`${API_URL}/verify`, {params: {token}});
|
||||
await Axios.get(`${PUSHER_URL}/verify`, {params: {token}});
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data);
|
||||
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then(res => res.data);
|
||||
this.localUser = new LocalUser(data.userUuid, data.authToken, []);
|
||||
if (!isBenchmark) { // In benchmark, we don't have a local storage.
|
||||
localUserStore.saveUser(this.localUser);
|
||||
@ -105,7 +108,7 @@ class ConnectionManager {
|
||||
}).catch((err) => {
|
||||
// Let's retry in 4-6 seconds
|
||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
this.reconnectingTimeout = setTimeout(() => {
|
||||
//todo: allow a way to break recursion?
|
||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
||||
|
@ -1,12 +1,16 @@
|
||||
import {LocalUser} from "./LocalUser";
|
||||
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const playerNameKey = 'playerName';
|
||||
const selectedPlayerKey = 'selectedPlayer';
|
||||
const customCursorPositionKey = 'customCursorPosition';
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const audioPlayerVolumeKey = 'audioVolume';
|
||||
const audioPlayerMuteKey = 'audioMute';
|
||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||
|
||||
//todo: add localstorage fallback
|
||||
class LocalUserStore {
|
||||
|
||||
saveUser(localUser: LocalUser) {
|
||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
||||
}
|
||||
@ -14,48 +18,69 @@ class LocalUserStore {
|
||||
const data = localStorage.getItem('localUser');
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
|
||||
setName(name:string): void {
|
||||
window.localStorage.setItem('playerName', name);
|
||||
localStorage.setItem(playerNameKey, name);
|
||||
}
|
||||
getName(): string {
|
||||
return window.localStorage.getItem('playerName') ?? '';
|
||||
return localStorage.getItem(playerNameKey) || '';
|
||||
}
|
||||
|
||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
|
||||
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
||||
}
|
||||
getPlayerCharacterIndex(): number {
|
||||
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
|
||||
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
||||
}
|
||||
|
||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
||||
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
|
||||
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
|
||||
}
|
||||
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
||||
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
|
||||
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||
}
|
||||
|
||||
setCharacterLayers(layers: string[]): void {
|
||||
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
}
|
||||
getCharacterLayers(): string[]|null {
|
||||
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
|
||||
}
|
||||
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
|
||||
return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||
}
|
||||
|
||||
setGameQualityValue(value: number): void {
|
||||
localStorage.setItem(gameQualityKey, '' + value);
|
||||
}
|
||||
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
||||
}
|
||||
|
||||
setVideoQualityValue(value: number): void {
|
||||
localStorage.setItem(videoQualityKey, '' + value);
|
||||
}
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
||||
}
|
||||
|
||||
setAudioPlayerVolume(value: number): void {
|
||||
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
||||
}
|
||||
getAudioPlayerVolume(): number {
|
||||
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
||||
}
|
||||
|
||||
setAudioPlayerMuted(value: boolean): void {
|
||||
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||
}
|
||||
getAudioPlayerMuted(): boolean {
|
||||
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
||||
}
|
||||
|
||||
setHelpCameraSettingsShown(): void {
|
||||
localStorage.setItem(helpCameraSettingsShown, '1');
|
||||
}
|
||||
getHelpCameraSettingsShown(): boolean {
|
||||
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
||||
}
|
||||
}
|
||||
|
||||
export const localUserStore = new LocalUserStore();
|
||||
export const localUserStore = new LocalUserStore();
|
||||
|
@ -1,29 +1,30 @@
|
||||
import Axios from "axios";
|
||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
||||
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
||||
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private mapUrl: string|undefined;
|
||||
private instance: string|undefined;
|
||||
private _search: URLSearchParams;
|
||||
|
||||
constructor(id: string) {
|
||||
if (id.startsWith('/')) {
|
||||
id = id.substr(1);
|
||||
const url = new URL(id, 'https://example.com');
|
||||
|
||||
this.id = url.pathname;
|
||||
|
||||
if (this.id.startsWith('/')) {
|
||||
this.id = this.id.substr(1);
|
||||
}
|
||||
this.id = id;
|
||||
if (id.startsWith('_/')) {
|
||||
if (this.id.startsWith('_/')) {
|
||||
this.isPublic = true;
|
||||
} else if (id.startsWith('@/')) {
|
||||
} else if (this.id.startsWith('@/')) {
|
||||
this.isPublic = false;
|
||||
} else {
|
||||
throw new Error('Invalid room ID');
|
||||
}
|
||||
|
||||
const indexOfHash = this.id.indexOf('#');
|
||||
if (indexOfHash !== -1) {
|
||||
this.id = this.id.substr(0, indexOfHash);
|
||||
}
|
||||
this._search = new URLSearchParams(url.search);
|
||||
}
|
||||
|
||||
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
|
||||
@ -66,7 +67,7 @@ export class Room {
|
||||
// We have a private ID, we need to query the map URL from the server.
|
||||
const urlParts = this.parsePrivateUrl(this.id);
|
||||
|
||||
Axios.get(`${API_URL}/map`, {
|
||||
Axios.get(`${PUSHER_URL}/map`, {
|
||||
params: urlParts
|
||||
}).then(({data}) => {
|
||||
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
||||
@ -117,4 +118,17 @@ export class Room {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public isDisconnected(): boolean
|
||||
{
|
||||
const alone = this._search.get('alone');
|
||||
if (alone && alone !== '0' && alone.toLowerCase() !== 'false') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public get search(): URLSearchParams {
|
||||
return this._search;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import {
|
||||
BatchMessage,
|
||||
@ -43,7 +43,9 @@ import {
|
||||
} from "./ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import {adminMessagesService} from "./AdminMessagesService";
|
||||
import {connectionManager, ConnexionMessageEventTypes} from "./ConnectionManager";
|
||||
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||
import {connectionManager} from "./ConnectionManager";
|
||||
|
||||
const manualPingDelay = 20000;
|
||||
|
||||
@ -65,8 +67,12 @@ export class RoomConnection implements RoomConnection {
|
||||
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
||||
*/
|
||||
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) {
|
||||
let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||
url += '/room';
|
||||
let url = new URL(PUSHER_URL, window.location.toString()).toString();
|
||||
url = url.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||
if (!url.endsWith('/')) {
|
||||
url += '/';
|
||||
}
|
||||
url += 'room';
|
||||
url += '?roomId='+(roomId ?encodeURIComponent(roomId):'');
|
||||
url += '&token='+(token ?encodeURIComponent(token):'');
|
||||
url += '&name='+encodeURIComponent(name);
|
||||
@ -156,8 +162,8 @@ export class RoomConnection implements RoomConnection {
|
||||
items
|
||||
} as RoomJoinedMessageInterface
|
||||
});
|
||||
} else if (message.hasErrormessage()) {
|
||||
connectionManager._connexionMessageStream.next({type: ConnexionMessageEventTypes.worldFull}); //todo: generalize this behavior to all messages
|
||||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||
@ -179,6 +185,10 @@ export class RoomConnection implements RoomConnection {
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
||||
} else if (message.hasBanusermessage()) {
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
||||
} else if (message.hasWorldfullwarningmessage()) {
|
||||
worldFullWarningStream.onMessage();
|
||||
} else if (message.hasRefreshroommessage()) {
|
||||
//todo: implement a way to notify the user the room was refreshed.
|
||||
} else {
|
||||
throw new Error('Unknown message received');
|
||||
}
|
||||
@ -378,9 +388,6 @@ export class RoomConnection implements RoomConnection {
|
||||
this.socket.addEventListener('error', callback)
|
||||
}
|
||||
|
||||
/*public onConnect(callback: (e: Event) => void): void {
|
||||
this.socket.addEventListener('open', callback)
|
||||
}*/
|
||||
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
||||
//this.socket.addEventListener('open', callback)
|
||||
this.onMessage(EventMessage.CONNECT, callback);
|
||||
@ -449,9 +456,9 @@ export class RoomConnection implements RoomConnection {
|
||||
});
|
||||
}
|
||||
|
||||
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
|
||||
public onServerDisconnected(callback: () => void): void {
|
||||
this.socket.addEventListener('close', (event) => {
|
||||
if (this.closed === true) {
|
||||
if (this.closed === true || connectionManager.unloading) {
|
||||
return;
|
||||
}
|
||||
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
||||
@ -459,7 +466,7 @@ export class RoomConnection implements RoomConnection {
|
||||
// Normal closure case
|
||||
return;
|
||||
}
|
||||
callback(event);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
|
14
front/src/Connexion/WorldFullMessageStream.ts
Normal file
14
front/src/Connexion/WorldFullMessageStream.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullMessageStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullMessageStream = new WorldFullMessageStream();
|
14
front/src/Connexion/WorldFullWarningStream.ts
Normal file
14
front/src/Connexion/WorldFullWarningStream.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullWarningStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullWarningStream = new WorldFullWarningStream();
|
@ -1,8 +1,9 @@
|
||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
||||
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost");
|
||||
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
|
||||
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost");
|
||||
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
|
||||
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
|
||||
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost";
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
const TURN_USER: string = process.env.TURN_USER || '';
|
||||
@ -17,7 +18,7 @@ const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new
|
||||
export {
|
||||
DEBUG_MODE,
|
||||
START_ROOM_URL,
|
||||
API_URL,
|
||||
PUSHER_URL,
|
||||
UPLOADER_URL,
|
||||
ADMIN_URL,
|
||||
RESOLUTION,
|
||||
|
14
front/src/Phaser/Components/WarningContainer.ts
Normal file
14
front/src/Phaser/Components/WarningContainer.ts
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
export const warningContainerKey = 'warningContainer';
|
||||
export const warningContainerHtml = 'resources/html/warningContainer.html';
|
||||
|
||||
export class WarningContainer extends Phaser.GameObjects.DOMElement {
|
||||
|
||||
constructor(scene: Phaser.Scene) {
|
||||
super(scene, 100, 0);
|
||||
this.setOrigin(0, 0);
|
||||
this.createFromCache(warningContainerKey);
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
}
|
@ -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,10 @@ export class GameManager {
|
||||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
||||
scenePlugin.launch(MenuSceneName);
|
||||
|
||||
if (!localUserStore.getHelpCameraSettingsShown()) {
|
||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||
}
|
||||
}
|
||||
|
||||
public gameSceneIsCreated(scene: GameScene) {
|
||||
|
@ -29,7 +29,9 @@ import {
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
TRIGGER_JITSI_PROPERTIES,
|
||||
TRIGGER_WEBSITE_PROPERTIES,
|
||||
WEBSITE_MESSAGE_PROPERTIES
|
||||
WEBSITE_MESSAGE_PROPERTIES,
|
||||
AUDIO_VOLUME_PROPERTY,
|
||||
AUDIO_LOOP_PROPERTY
|
||||
} from "../../WebRtc/LayoutManager";
|
||||
import {GameMap} from "./GameMap";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
@ -39,7 +41,7 @@ import {ActionableItem} from "../Items/ActionableItem";
|
||||
import {UserInputManager} from "../UserInput/UserInputManager";
|
||||
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
||||
import {connectionManager, ConnexionMessageEvent, ConnexionMessageEventTypes} from "../../Connexion/ConnectionManager";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||
import {userMessageManager} from "../../Administration/UserMessageManager";
|
||||
@ -66,6 +68,7 @@ import GameObject = Phaser.GameObjects.GameObject;
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import {Subscription} from "rxjs";
|
||||
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface|null,
|
||||
@ -191,6 +194,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
|
||||
this.load.image(openChatIconName, 'resources/objects/talk.png');
|
||||
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
||||
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
||||
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:')) {
|
||||
this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://');
|
||||
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
||||
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||
this.onMapLoad(data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Network error',
|
||||
subTitle: 'An error occurred while loading resource:',
|
||||
@ -315,7 +327,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
urlManager.pushRoomIdToUrl(this.room);
|
||||
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||
|
||||
this.messageSubscription = connectionManager._connexionMessageStream.subscribe((event) => this.onConnexionMessage(event))
|
||||
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
|
||||
|
||||
const playerName = gameManager.getPlayerName();
|
||||
if (!playerName) {
|
||||
@ -381,19 +393,21 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.initCirclesCanvas();
|
||||
|
||||
// Let's pause the scene if the connection is not established yet
|
||||
if (this.isReconnecting) {
|
||||
setTimeout(() => {
|
||||
this.scene.sleep();
|
||||
this.scene.launch(ReconnectingSceneName);
|
||||
}, 0);
|
||||
} else if (this.connection === undefined) {
|
||||
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
|
||||
setTimeout(() => {
|
||||
if (this.connection === undefined) {
|
||||
if (!this.room.isDisconnected()) {
|
||||
if (this.isReconnecting) {
|
||||
setTimeout(() => {
|
||||
this.scene.sleep();
|
||||
this.scene.launch(ReconnectingSceneName);
|
||||
}
|
||||
}, 1000);
|
||||
}, 0);
|
||||
} else if (this.connection === undefined) {
|
||||
// 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);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
this.createPromiseResolve();
|
||||
@ -420,6 +434,16 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
this.listenToIframeEvents();
|
||||
|
||||
|
||||
if (!this.room.isDisconnected()) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the connection to Pusher.
|
||||
*/
|
||||
private connect(): void {
|
||||
const camera = this.cameras.main;
|
||||
|
||||
connectionManager.connectToRoomSocket(
|
||||
@ -467,16 +491,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
});
|
||||
|
||||
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
||||
audioManager.decreaseVolume();
|
||||
this.shareGroupPosition(groupPositionMessage);
|
||||
this.openChatIcon.setVisible(true);
|
||||
})
|
||||
|
||||
this.connection.onGroupDeleted((groupId: number) => {
|
||||
audioManager.restoreVolume();
|
||||
try {
|
||||
this.deleteGroup(groupId);
|
||||
this.openChatIcon.setVisible(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -484,9 +504,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
|
||||
this.connection.onServerDisconnected(() => {
|
||||
console.log('Player disconnected from server. Reloading scene.');
|
||||
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer.unregister();
|
||||
this.cleanupClosingScene();
|
||||
|
||||
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
||||
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
||||
@ -529,11 +547,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
onConnect(user: UserSimplePeerInterface) {
|
||||
self.presentationModeSprite.setVisible(true);
|
||||
self.chatModeSprite.setVisible(true);
|
||||
self.openChatIcon.setVisible(true);
|
||||
audioManager.decreaseVolume();
|
||||
},
|
||||
onDisconnect(userId: number) {
|
||||
if (self.simplePeer.getNbConnections() === 0) {
|
||||
self.presentationModeSprite.setVisible(false);
|
||||
self.chatModeSprite.setVisible(false);
|
||||
self.openChatIcon.setVisible(false);
|
||||
audioManager.restoreVolume();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -641,7 +663,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
}else{
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||
if (JITSI_PRIVATE_MODE) {
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||
|
||||
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
@ -672,12 +695,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.connection.setSilent(true);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined;
|
||||
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined;
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
|
||||
});
|
||||
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange('zone', (newValue, oldValue) => {
|
||||
@ -820,6 +845,7 @@ ${escapedMessage}
|
||||
audioManager.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
this.connection?.closeConnection();
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer?.unregister();
|
||||
this.messageSubscription?.unsubscribe();
|
||||
}
|
||||
@ -942,7 +968,7 @@ ${escapedMessage}
|
||||
const y = Math.floor(key / layer.width);
|
||||
const x = key % layer.width;
|
||||
|
||||
possibleStartPositions.push({x: x*32, y: y*32});
|
||||
possibleStartPositions.push({x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth});
|
||||
});
|
||||
// Get a value at random amongst allowed values
|
||||
if (possibleStartPositions.length === 0) {
|
||||
@ -1124,13 +1150,12 @@ ${escapedMessage}
|
||||
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);
|
||||
});
|
||||
@ -1348,8 +1373,9 @@ ${escapedMessage}
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig');
|
||||
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig');
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig);
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||
this.connection.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
@ -1367,7 +1393,7 @@ ${escapedMessage}
|
||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||
}
|
||||
|
||||
//todo: into onConnexionMessage
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private bannedUser(){
|
||||
this.cleanupClosingScene();
|
||||
this.userInputManager.disableControls();
|
||||
@ -1378,16 +1404,15 @@ ${escapedMessage}
|
||||
});
|
||||
}
|
||||
|
||||
private onConnexionMessage(event: ConnexionMessageEvent) {
|
||||
if (event.type === ConnexionMessageEventTypes.worldFull) {
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop(ReconnectingSceneName);
|
||||
this.userInputManager.disableControls();
|
||||
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'
|
||||
});
|
||||
}
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private showWorldFullError(): void {
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop(ReconnectingSceneName);
|
||||
this.userInputManager.disableControls();
|
||||
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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -62,26 +61,22 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
|
||||
|
||||
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowRight.setOrigin(0.5, 0.5);
|
||||
this.arrowRight.setVisible(false);
|
||||
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
|
||||
this.add.existing(this.arrowRight);
|
||||
|
||||
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowLeft.setOrigin(0.5, 0.5);
|
||||
this.arrowLeft.setVisible(false);
|
||||
this.arrowLeft.flipX = true;
|
||||
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
|
||||
this.add.existing(this.arrowLeft);
|
||||
|
||||
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowUp.setOrigin(0.5, 0.5);
|
||||
this.arrowUp.setVisible(false);
|
||||
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
|
||||
this.add.existing(this.arrowUp);
|
||||
|
||||
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowDown.setOrigin(0.5, 0.5);
|
||||
this.arrowDown.setVisible(false);
|
||||
this.arrowDown.flipY = true;
|
||||
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
||||
@ -165,8 +160,6 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
|
||||
private updateWebCamName(): void {
|
||||
if (this.camerasList.length > 1) {
|
||||
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||
|
||||
let label = this.camerasList[this.cameraSelected].label;
|
||||
// remove text in parenthesis
|
||||
label = label.replace(/\([^()]*\)/g, '').trim();
|
||||
@ -174,17 +167,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
this.cameraNameField.text = label;
|
||||
|
||||
if (this.cameraSelected < this.camerasList.length - 1) {
|
||||
this.arrowRight.setVisible(true);
|
||||
} else {
|
||||
this.arrowRight.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.cameraSelected > 0) {
|
||||
this.arrowLeft.setVisible(true);
|
||||
} else {
|
||||
this.arrowLeft.setVisible(false);
|
||||
}
|
||||
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
|
||||
this.arrowLeft.setVisible(this.cameraSelected > 0);
|
||||
}
|
||||
if (this.microphonesList.length > 1) {
|
||||
let label = this.microphonesList[this.microphoneSelected].label;
|
||||
@ -195,17 +179,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
||||
|
||||
this.microphoneNameField.text = label;
|
||||
|
||||
if (this.microphoneSelected < this.microphonesList.length - 1) {
|
||||
this.arrowDown.setVisible(true);
|
||||
} else {
|
||||
this.arrowDown.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.microphoneSelected > 0) {
|
||||
this.arrowUp.setVisible(true);
|
||||
} else {
|
||||
this.arrowUp.setVisible(false);
|
||||
}
|
||||
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
|
||||
this.arrowUp.setVisible(this.microphoneSelected > 0);
|
||||
|
||||
}
|
||||
this.reposition();
|
||||
|
95
front/src/Phaser/Menu/HelpCameraSettingsScene.ts
Normal file
95
front/src/Phaser/Menu/HelpCameraSettingsScene.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
|
||||
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(){
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
||||
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
||||
|
||||
export const MenuSceneName = 'MenuScene';
|
||||
const gameMenuKey = 'gameMenu';
|
||||
@ -30,6 +32,8 @@ export class MenuScene extends Phaser.Scene {
|
||||
private gameQualityValue: number;
|
||||
private videoQualityValue: number;
|
||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
||||
private warningContainer: WarningContainer | null = null;
|
||||
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor() {
|
||||
super({key: MenuSceneName});
|
||||
@ -44,6 +48,7 @@ export class MenuScene extends Phaser.Scene {
|
||||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||
this.load.html(gameShare, 'resources/html/gameShare.html');
|
||||
this.load.html(gameReportKey, gameReportRessource);
|
||||
this.load.html(warningContainerKey, warningContainerHtml);
|
||||
}
|
||||
|
||||
create() {
|
||||
@ -85,6 +90,8 @@ export class MenuScene extends Phaser.Scene {
|
||||
|
||||
this.menuElement.addListener('click');
|
||||
this.menuElement.on('click', this.onMenuClick.bind(this));
|
||||
|
||||
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
|
||||
}
|
||||
|
||||
//todo put this method in a parent menuElement class
|
||||
@ -121,6 +128,21 @@ export class MenuScene extends Phaser.Scene {
|
||||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
private showWorldCapacityWarning() {
|
||||
if (!this.warningContainer) {
|
||||
this.warningContainer = new WarningContainer(this);
|
||||
}
|
||||
if (this.warningContainerTimeout) {
|
||||
clearTimeout(this.warningContainerTimeout);
|
||||
}
|
||||
this.warningContainerTimeout = setTimeout(() => {
|
||||
this.warningContainer?.destroy();
|
||||
this.warningContainer = null
|
||||
this.warningContainerTimeout = null
|
||||
}, 120000);
|
||||
|
||||
}
|
||||
|
||||
private closeSideMenu(): void {
|
||||
if (!this.sideMenuOpened) return;
|
||||
|
@ -35,7 +35,8 @@ class UrlManager {
|
||||
public pushRoomIdToUrl(room:Room): void {
|
||||
if (window.location.pathname === room.id) return;
|
||||
const hash = window.location.hash;
|
||||
history.pushState({}, 'WorkAdventure', room.id+hash);
|
||||
const search = room.search.toString();
|
||||
history.pushState({}, 'WorkAdventure', room.id+(search?'?'+search:'')+hash);
|
||||
}
|
||||
|
||||
public getStartLayerNameFromUrl(): string|null {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {isUndefined} from "generic-type-guard";
|
||||
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||
|
||||
enum audioStates {
|
||||
closed = 0,
|
||||
@ -9,6 +10,8 @@ enum audioStates {
|
||||
|
||||
const audioPlayerDivId = "audioplayer";
|
||||
const audioPlayerCtrlId = "audioplayerctrl";
|
||||
const audioPlayerVolId = "audioplayer_volume";
|
||||
const audioPlayerMuteId = "audioplayer_volume_icon_playing";
|
||||
const animationTime = 500;
|
||||
|
||||
class AudioManager {
|
||||
@ -17,6 +20,8 @@ class AudioManager {
|
||||
private audioPlayerDiv: HTMLDivElement;
|
||||
private audioPlayerCtrl: HTMLDivElement;
|
||||
private audioPlayerElem: HTMLAudioElement | undefined;
|
||||
private audioPlayerVol: HTMLInputElement;
|
||||
private audioPlayerMute: HTMLInputElement;
|
||||
|
||||
private volume = 1;
|
||||
private muted = false;
|
||||
@ -26,19 +31,19 @@ class AudioManager {
|
||||
constructor() {
|
||||
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
|
||||
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
|
||||
this.audioPlayerVol = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerVolId);
|
||||
this.audioPlayerMute = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerMuteId);
|
||||
|
||||
const storedVolume = localStorage.getItem('volume')
|
||||
if (storedVolume === null) {
|
||||
this.setVolume(1);
|
||||
} else {
|
||||
this.volume = parseFloat(storedVolume);
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = storedVolume;
|
||||
this.volume = localUserStore.getAudioPlayerVolume();
|
||||
this.audioPlayerVol.value = '' + this.volume;
|
||||
|
||||
this.muted = localUserStore.getAudioPlayerMuted();
|
||||
if (this.muted) {
|
||||
this.audioPlayerMute.classList.add('muted');
|
||||
}
|
||||
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
|
||||
}
|
||||
|
||||
public playAudio(url: string|number|boolean, mapDirUrl: string, loop=false): void {
|
||||
public playAudio(url: string|number|boolean, mapDirUrl: string, volume: number|undefined, loop=false): void {
|
||||
const audioPath = url as string;
|
||||
let realAudioPath = '';
|
||||
|
||||
@ -50,7 +55,7 @@ class AudioManager {
|
||||
realAudioPath = mapDirUrl + '/' + url;
|
||||
}
|
||||
|
||||
this.loadAudio(realAudioPath);
|
||||
this.loadAudio(realAudioPath, volume);
|
||||
|
||||
if (loop) {
|
||||
this.loop();
|
||||
@ -75,26 +80,29 @@ class AudioManager {
|
||||
}
|
||||
|
||||
private changeVolume(talking = false): void {
|
||||
if (!isUndefined(this.audioPlayerElem)) {
|
||||
this.audioPlayerElem.volume = this.naturalVolume(talking && this.decreaseWhileTalking);
|
||||
this.audioPlayerElem.muted = this.muted;
|
||||
if (isUndefined(this.audioPlayerElem)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private naturalVolume(makeSofter: boolean = false): number {
|
||||
const volume = this.volume
|
||||
const retVol = makeSofter && !this.volumeReduced ? Math.pow(volume * 0.5, 3) : volume
|
||||
this.volumeReduced = makeSofter
|
||||
return retVol;
|
||||
const reduceVolume = talking && this.decreaseWhileTalking;
|
||||
if (reduceVolume && !this.volumeReduced) {
|
||||
this.volume *= 0.5;
|
||||
} else if (!reduceVolume && this.volumeReduced) {
|
||||
this.volume *= 2.0;
|
||||
}
|
||||
this.volumeReduced = reduceVolume;
|
||||
|
||||
this.audioPlayerElem.volume = this.volume;
|
||||
this.audioPlayerVol.value = '' + this.volume;
|
||||
this.audioPlayerElem.muted = this.muted;
|
||||
}
|
||||
|
||||
private setVolume(volume: number): void {
|
||||
this.volume = volume;
|
||||
localStorage.setItem('volume', '' + volume);
|
||||
localUserStore.setAudioPlayerVolume(volume);
|
||||
}
|
||||
|
||||
|
||||
private loadAudio(url: string): void {
|
||||
private loadAudio(url: string, volume: number|undefined): void {
|
||||
this.load();
|
||||
|
||||
/* Solution 1, remove whole audio player */
|
||||
@ -112,23 +120,24 @@ class AudioManager {
|
||||
this.audioPlayerElem.append(srcElem);
|
||||
|
||||
this.audioPlayerDiv.append(this.audioPlayerElem);
|
||||
this.volume = volume ? Math.min(volume, this.volume) : this.volume;
|
||||
this.changeVolume();
|
||||
this.audioPlayerElem.play();
|
||||
|
||||
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
|
||||
muteElem.onclick = (ev: Event)=> {
|
||||
muteElem.onclick = (ev: Event) => {
|
||||
this.muted = !this.muted;
|
||||
this.changeVolume();
|
||||
localUserStore.setAudioPlayerMuted(this.muted);
|
||||
|
||||
if (this.muted) {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.add('muted');
|
||||
this.audioPlayerMute.classList.add('muted');
|
||||
} else {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.remove('muted');
|
||||
this.audioPlayerMute.classList.remove('muted');
|
||||
}
|
||||
}
|
||||
|
||||
const volumeElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume');
|
||||
volumeElem.oninput = (ev: Event)=> {
|
||||
this.audioPlayerVol.oninput = (ev: Event)=> {
|
||||
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
|
||||
this.changeVolume();
|
||||
|
||||
|
@ -1,37 +1,109 @@
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {Subject} from "rxjs";
|
||||
import {iframeListener} from "../Api/IframeListener";
|
||||
|
||||
export type CoWebsiteStateChangedCallback = () => void;
|
||||
|
||||
enum iframeStates {
|
||||
closed = 1,
|
||||
loading, // loading an iframe can be slow, so we show some placeholder until it is ready
|
||||
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 === window.innerHeight : this.width === window.innerWidth;
|
||||
}
|
||||
|
||||
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.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
|
||||
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 getDevicePixelRatio(): number {
|
||||
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
|
||||
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
|
||||
return window.navigator.userAgent.includes('Firefox') ? 1 : window.devicePixelRatio;
|
||||
}
|
||||
|
||||
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
|
||||
@ -41,18 +113,23 @@ 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 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, allowApi?: boolean, 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';
|
||||
@ -66,7 +143,7 @@ class CoWebsiteManager {
|
||||
if (allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
this.cowebsiteDiv.appendChild(iframe);
|
||||
this.cowebsiteMainDom.appendChild(iframe);
|
||||
const onTimeoutPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
@ -83,7 +160,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();
|
||||
@ -101,9 +179,7 @@ class CoWebsiteManager {
|
||||
iframeListener.unregisterIframe(iframe);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg">
|
||||
</button>`;
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
resolve();
|
||||
}, animationTime)
|
||||
}));
|
||||
@ -117,27 +193,35 @@ 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 {
|
||||
this._onResize.next();
|
||||
}
|
||||
|
||||
private fire(): void {
|
||||
for (const callback of this.observers) {
|
||||
callback();
|
||||
private fullscreen(): void {
|
||||
if (this.isFullScreen) {
|
||||
this.resetStyle();
|
||||
this.fire();
|
||||
//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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ class JitsiFactory {
|
||||
private audioCallback = this.onAudioChange.bind(this);
|
||||
private videoCallback = this.onVideoChange.bind(this);
|
||||
private previousConfigMeet? : jitsiConfigInterface;
|
||||
private jitsiScriptLoaded: boolean = false;
|
||||
|
||||
/**
|
||||
* Slugifies the room name and prepends the room name with the instance
|
||||
@ -80,11 +81,11 @@ class JitsiFactory {
|
||||
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||
}
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object): void {
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
|
||||
//save previous config
|
||||
this.previousConfigMeet = getDefaultConfig();
|
||||
|
||||
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
@ -93,7 +94,12 @@ class JitsiFactory {
|
||||
// clear jitsi local storage before starting a new conference.
|
||||
window.localStorage.removeItem("jitsiLocalStorage");
|
||||
|
||||
const domain = JITSI_URL;
|
||||
const domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error('Missing JITSI_URL environment variable or jitsiUrl parameter in the map.')
|
||||
}
|
||||
await this.loadJitsiScript(domain);
|
||||
|
||||
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
@ -157,6 +163,32 @@ class JitsiFactory {
|
||||
mediaManager.enableCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadJitsiScript(domain: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.jitsiScriptLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.jitsiScriptLoaded = true;
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + domain + '/external_api.js';
|
||||
jitsiScript.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
jitsiScript.onerror = () => {
|
||||
reject();
|
||||
}
|
||||
|
||||
document.head.appendChild(jitsiScript);
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const jitsiFactory = new JitsiFactory();
|
||||
|
@ -31,6 +31,9 @@ export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
|
||||
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
|
||||
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
|
||||
|
||||
export const AUDIO_VOLUME_PROPERTY = 'audioVolume';
|
||||
export const AUDIO_LOOP_PROPERTY = 'audioLoop';
|
||||
|
||||
/**
|
||||
* This class is in charge of the video-conference layout.
|
||||
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
||||
|
@ -2,19 +2,14 @@ import {DivImportance, layoutManager} from "./LayoutManager";
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
|
||||
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
|
||||
let valueVideo = 20;
|
||||
if(localValueVideo){
|
||||
valueVideo = parseInt(localValueVideo);
|
||||
}
|
||||
let videoConstraint: boolean|MediaTrackConstraints = {
|
||||
width: { min: 640, ideal: 1280, max: 1920 },
|
||||
height: { min: 400, ideal: 720 },
|
||||
frameRate: { ideal: valueVideo },
|
||||
frameRate: { ideal: localUserStore.getVideoQualityValue() },
|
||||
facingMode: "user",
|
||||
resizeMode: 'crop-and-scale',
|
||||
aspectRatio: 1.777777778
|
||||
|
@ -63,7 +63,7 @@ export class SimplePeer {
|
||||
}
|
||||
|
||||
public getNbConnections(): number {
|
||||
return this.PeerConnectionArray.size;
|
||||
return this.Users.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,9 +230,6 @@ export class SimplePeer {
|
||||
|
||||
this.closeScreenSharingConnection(userId);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
||||
if(userIndex < 0){
|
||||
throw 'Couln\'t delete user';
|
||||
@ -250,6 +247,10 @@ export class SimplePeer {
|
||||
this.PeerScreenSharingConnectionArray.delete(userId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,29 +1,23 @@
|
||||
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";
|
||||
import {iframeListener} from "./Api/IframeListener";
|
||||
import {discussionManager} from "./WebRtc/DiscussionManager";
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
if (JITSI_URL) {
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + JITSI_URL + '/external_api.js';
|
||||
document.head.appendChild(jitsiScript);
|
||||
}
|
||||
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
|
||||
const valueGameQuality = localUserStore.getGameQualityValue();
|
||||
@ -80,7 +74,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: {
|
||||
@ -111,6 +105,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);
|
||||
|
||||
@ -122,7 +117,7 @@ window.addEventListener('resize', function (event) {
|
||||
}
|
||||
});
|
||||
|
||||
coWebsiteManager.onStateChange(() => {
|
||||
coWebsiteManager.onResize.subscribe(() => {
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||
});
|
||||
|
@ -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: {
|
||||
@ -11,6 +12,7 @@ module.exports = {
|
||||
devServer: {
|
||||
contentBase: './dist',
|
||||
host: '0.0.0.0',
|
||||
sockPort: 80,
|
||||
disableHostCheck: true,
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
@ -26,6 +28,10 @@ module.exports = {
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
@ -44,6 +50,7 @@ module.exports = {
|
||||
require('webpack-require-http')
|
||||
],
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
|
||||
new HtmlWebpackPlugin(
|
||||
{
|
||||
template: './dist/index.tmpl.html.tmp',
|
||||
@ -62,19 +69,20 @@ module.exports = {
|
||||
new webpack.ProvidePlugin({
|
||||
Phaser: 'phaser'
|
||||
}),
|
||||
new webpack.EnvironmentPlugin([
|
||||
'API_URL',
|
||||
'UPLOADER_URL',
|
||||
'ADMIN_URL',
|
||||
'DEBUG_MODE',
|
||||
'STUN_SERVER',
|
||||
'TURN_SERVER',
|
||||
'TURN_USER',
|
||||
'TURN_PASSWORD',
|
||||
'JITSI_URL',
|
||||
'JITSI_PRIVATE_MODE',
|
||||
'START_ROOM_URL'
|
||||
])
|
||||
new webpack.EnvironmentPlugin({
|
||||
'API_URL': null,
|
||||
'PUSHER_URL': undefined,
|
||||
'UPLOADER_URL': null,
|
||||
'ADMIN_URL': null,
|
||||
'DEBUG_MODE': null,
|
||||
'STUN_SERVER': null,
|
||||
'TURN_SERVER': null,
|
||||
'TURN_USER': null,
|
||||
'TURN_PASSWORD': null,
|
||||
'JITSI_URL': null,
|
||||
'JITSI_PRIVATE_MODE': null,
|
||||
'START_ROOM_URL': null
|
||||
})
|
||||
],
|
||||
|
||||
};
|
||||
|
227
front/yarn.lock
227
front/yarn.lock
@ -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==
|
||||
@ -5184,15 +5362,20 @@ xtend@^4.0.0, xtend@~4.0.1:
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||
integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
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"
|
||||
|
@ -1,4 +1,3 @@
|
||||
# we are rebuilding on each deploy to cope with the API_URL environment URL
|
||||
FROM thecodingmachine/nodejs:12-apache
|
||||
|
||||
COPY --chown=docker:docker . .
|
||||
|
@ -24,7 +24,7 @@
|
||||
{
|
||||
"name":"exitUrl",
|
||||
"type":"string",
|
||||
"value":"\/@\/tcm\/workadventure\/floor0"
|
||||
"value":"@\/tcm\/workadventure\/floor0"
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
@ -355,7 +355,7 @@
|
||||
"value":"scriptTuto.js"
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.5.0",
|
||||
"tiledversion":"2021.03.23",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
|
94
maps/tests/jitsi_custom_url.json
Normal file
94
maps/tests/jitsi_custom_url.json
Normal file
@ -0,0 +1,94 @@
|
||||
{ "compressionlevel":-1,
|
||||
"editorsettings":
|
||||
{
|
||||
"export":
|
||||
{
|
||||
"target":"."
|
||||
}
|
||||
},
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":2,
|
||||
"name":"start",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":5,
|
||||
"name":"jitsiConf",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"jitsiRoom",
|
||||
"type":"string",
|
||||
"value":"myRoom"
|
||||
},
|
||||
{
|
||||
"name":"jitsiUrl",
|
||||
"type":"string",
|
||||
"value":"meet.jit.si"
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":6,
|
||||
"nextobjectid":1,
|
||||
"orientation":"orthogonal",
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.3.3",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.2,
|
||||
"width":10
|
||||
}
|
@ -197,6 +197,22 @@ message SendUserMessage{
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message WorldFullWarningMessage{
|
||||
}
|
||||
message WorldFullWarningToRoomMessage{
|
||||
string roomId = 1;
|
||||
}
|
||||
message RefreshRoomPromptMessage{
|
||||
string roomId = 1;
|
||||
}
|
||||
message RefreshRoomMessage{
|
||||
string roomId = 1;
|
||||
int32 versionNumber = 2;
|
||||
}
|
||||
|
||||
message WorldFullMessage{
|
||||
}
|
||||
|
||||
message BanUserMessage{
|
||||
string type = 1;
|
||||
string message = 2;
|
||||
@ -218,6 +234,9 @@ message ServerToClientMessage {
|
||||
SendUserMessage sendUserMessage = 12;
|
||||
BanUserMessage banUserMessage = 13;
|
||||
AdminRoomMessage adminRoomMessage = 14;
|
||||
WorldFullWarningMessage worldFullWarningMessage = 15;
|
||||
WorldFullMessage worldFullMessage = 16;
|
||||
RefreshRoomMessage refreshRoomMessage = 17;
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,4 +402,6 @@ service RoomManager {
|
||||
rpc sendGlobalAdminMessage(AdminGlobalMessage) returns (EmptyMessage);
|
||||
rpc ban(BanMessage) returns (EmptyMessage);
|
||||
rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage);
|
||||
rpc sendWorldFullWarningToRoom(WorldFullWarningToRoomMessage) returns (EmptyMessage);
|
||||
rpc sendRefreshRoomPrompt(RefreshRoomPromptMessage) returns (EmptyMessage);
|
||||
}
|
||||
|
@ -4155,9 +4155,9 @@ xtend@~4.0.1:
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
@ -2,7 +2,7 @@ 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";
|
||||
import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb";
|
||||
|
||||
|
||||
export class AdminController extends BaseController{
|
||||
@ -11,6 +11,56 @@ export class AdminController extends BaseController{
|
||||
super();
|
||||
this.App = App;
|
||||
this.receiveGlobalMessagePrompt();
|
||||
this.receiveRoomEditionPrompt();
|
||||
}
|
||||
|
||||
receiveRoomEditionPrompt() {
|
||||
this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => {
|
||||
this.addCorsHeaders(res);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.post("/room/refresh", 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.roomId !== 'string') {
|
||||
throw 'Incorrect roomId parameter'
|
||||
}
|
||||
const roomId: string = body.roomId;
|
||||
|
||||
await apiClientRepository.getClient(roomId).then((roomClient) =>{
|
||||
return new Promise((res, rej) => {
|
||||
const roomMessage = new RefreshRoomPromptMessage();
|
||||
roomMessage.setRoomid(roomId);
|
||||
|
||||
roomClient.sendRefreshRoomPrompt(roomMessage, (err) => {
|
||||
err ? rej(err) : res();
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
this.errorToResponse(err, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeStatus("200");
|
||||
res.end('ok');
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
receiveGlobalMessagePrompt() {
|
||||
@ -40,22 +90,36 @@ export class AdminController extends BaseController{
|
||||
if (typeof body.text !== 'string') {
|
||||
throw 'Incorrect text parameter'
|
||||
}
|
||||
if (body.type !== 'capacity' && body.type !== 'message') {
|
||||
throw 'Incorrect type parameter'
|
||||
}
|
||||
if (!body.targets || typeof body.targets !== 'object') {
|
||||
throw 'Incorrect targets parameter'
|
||||
}
|
||||
const text: string = body.text;
|
||||
const type: string = body.type;
|
||||
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();
|
||||
});
|
||||
if (type === 'message') {
|
||||
const roomMessage = new AdminRoomMessage();
|
||||
roomMessage.setMessage(text);
|
||||
roomMessage.setRoomid(roomId);
|
||||
|
||||
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
|
||||
err ? rej(err) : res();
|
||||
});
|
||||
} else if (type === 'capacity') {
|
||||
const roomMessage = new WorldFullWarningToRoomMessage();
|
||||
roomMessage.setRoomid(roomId);
|
||||
|
||||
roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => {
|
||||
err ? rej(err) : res();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
@ -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 {
|
||||
@ -14,7 +13,20 @@ export class BaseController {
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected errorToResponse(e: any, res: HttpResponse): void {
|
||||
console.error("An error happened", e);
|
||||
if (e && e.message) {
|
||||
let url = e?.config?.url;
|
||||
if (url !== undefined) {
|
||||
url = ' for URL: '+url;
|
||||
} else {
|
||||
url = '';
|
||||
}
|
||||
console.error('ERROR: '+e.message+url);
|
||||
} else if (typeof(e) === 'string') {
|
||||
console.error(e);
|
||||
}
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
if (e.response) {
|
||||
res.writeStatus(e.response.status+" "+e.response.statusText);
|
||||
this.addCorsHeaders(res);
|
||||
|
@ -190,10 +190,10 @@ export class IoSocketController {
|
||||
memberMessages = userData.messages;
|
||||
memberTags = userData.tags;
|
||||
memberTextures = userData.textures;
|
||||
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
||||
if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
||||
throw new Error('No correct tags')
|
||||
}
|
||||
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
|
||||
if (!room.public && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
|
||||
throw new Error('No correct member')
|
||||
}
|
||||
} catch (e) {
|
||||
@ -258,12 +258,12 @@ export class IoSocketController {
|
||||
/* Handlers */
|
||||
open: (ws) => {
|
||||
if(ws.rejected === true) {
|
||||
emitError(ws, 'World is full');
|
||||
socketManager.emitWorldFullMessage(ws);
|
||||
ws.close();
|
||||
}
|
||||
|
||||
// Let's join the room
|
||||
const client = this.initClient(ws); //todo: into the upgrade instead?
|
||||
const client = this.initClient(ws);
|
||||
socketManager.handleJoinRoom(client);
|
||||
|
||||
//get data information and show messages
|
||||
|
@ -13,21 +13,22 @@ export enum GameRoomPolicyTypes {
|
||||
|
||||
export class PusherRoom {
|
||||
private readonly positionNotifier: PositionDispatcher;
|
||||
public readonly anonymous: boolean;
|
||||
public readonly public: boolean;
|
||||
public tags: string[];
|
||||
public policyType: GameRoomPolicyTypes;
|
||||
public readonly roomSlug: string;
|
||||
public readonly worldSlug: string = '';
|
||||
public readonly organizationSlug: string = '';
|
||||
private versionNumber: number = 1;
|
||||
|
||||
constructor(public readonly roomId: string,
|
||||
private socketListener: ZoneEventListener)
|
||||
{
|
||||
this.anonymous = isRoomAnonymous(roomId);
|
||||
this.public = isRoomAnonymous(roomId);
|
||||
this.tags = [];
|
||||
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
|
||||
|
||||
if (this.anonymous) {
|
||||
if (this.public) {
|
||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||
} else {
|
||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
||||
@ -55,4 +56,13 @@ export class PusherRoom {
|
||||
public isEmpty(): boolean {
|
||||
return this.positionNotifier.isEmpty();
|
||||
}
|
||||
|
||||
public needsUpdate(versionNumber: number): boolean {
|
||||
if (this.versionNumber < versionNumber) {
|
||||
this.versionNumber = versionNumber;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import {GameRoomPolicyTypes} from "_Model/PusherRoom";
|
||||
|
||||
export interface AdminApiData {
|
||||
organizationSlug: string
|
||||
@ -13,6 +14,13 @@ export interface AdminApiData {
|
||||
textures: CharacterTexture[]
|
||||
}
|
||||
|
||||
export interface MapDetailsData {
|
||||
roomSlug: string,
|
||||
mapUrl: string,
|
||||
policy_type: GameRoomPolicyTypes,
|
||||
tags: string[],
|
||||
}
|
||||
|
||||
export interface AdminBannedData {
|
||||
is_banned: boolean,
|
||||
message: string
|
||||
@ -35,9 +43,9 @@ export interface FetchMemberDataByUuidResponse {
|
||||
|
||||
class AdminApi {
|
||||
|
||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<MapDetailsData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
|
||||
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
||||
@ -60,7 +68,7 @@ class AdminApi {
|
||||
|
||||
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
const res = await Axios.get(ADMIN_API_URL+'/api/room/access',
|
||||
{ params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
||||
@ -70,7 +78,7 @@ class AdminApi {
|
||||
|
||||
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('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,
|
||||
@ -81,7 +89,7 @@ class AdminApi {
|
||||
|
||||
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('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,
|
||||
@ -104,7 +112,7 @@ class AdminApi {
|
||||
|
||||
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('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.
|
||||
return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
|
||||
|
@ -19,15 +19,15 @@ import {
|
||||
JoinRoomMessage,
|
||||
CharacterLayerMessage,
|
||||
PusherToBackMessage,
|
||||
WorldFullMessage,
|
||||
AdminPusherToBackMessage,
|
||||
ServerToAdminClientMessage,
|
||||
SendUserMessage,
|
||||
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage
|
||||
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
||||
import {adminApi, CharacterTexture} from "./AdminApi";
|
||||
import {emitError, emitInBatch} from "./IoSocketHelpers";
|
||||
import {emitInBatch} from "./IoSocketHelpers";
|
||||
import Jwt from "jsonwebtoken";
|
||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
||||
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
||||
@ -36,6 +36,7 @@ import {apiClientRepository} from "./ApiClientRepository";
|
||||
import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone";
|
||||
import Debug from "debug";
|
||||
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
|
||||
import {WebSocket} from "uWebSockets.js";
|
||||
|
||||
const debug = Debug('socket');
|
||||
|
||||
@ -52,7 +53,8 @@ export interface AdminSocketData {
|
||||
}
|
||||
|
||||
export class SocketManager implements ZoneEventListener {
|
||||
private Worlds: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
||||
|
||||
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
||||
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
||||
|
||||
constructor() {
|
||||
@ -178,6 +180,11 @@ export class SocketManager implements ZoneEventListener {
|
||||
// If this is the first message sent, send back the viewport.
|
||||
this.handleViewport(client, viewport);
|
||||
}
|
||||
|
||||
if (message.hasRefreshroommessage()) {
|
||||
const refreshMessage:RefreshRoomMessage = message.getRefreshroommessage() as unknown as RefreshRoomMessage;
|
||||
this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber())
|
||||
}
|
||||
|
||||
// Let's pass data over from the back to the client.
|
||||
if (!client.disconnecting) {
|
||||
@ -217,7 +224,7 @@ export class SocketManager implements ZoneEventListener {
|
||||
try {
|
||||
client.viewport = viewport;
|
||||
|
||||
const world = this.Worlds.get(client.roomId);
|
||||
const world = this.rooms.get(client.roomId);
|
||||
if (!world) {
|
||||
console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'");
|
||||
return;
|
||||
@ -308,12 +315,12 @@ export class SocketManager implements ZoneEventListener {
|
||||
if (socket.roomId) {
|
||||
try {
|
||||
//user leaves room
|
||||
const room: PusherRoom | undefined = this.Worlds.get(socket.roomId);
|
||||
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
||||
if (room) {
|
||||
debug('Leaving room %s.', socket.roomId);
|
||||
room.leave(socket);
|
||||
if (room.isEmpty()) {
|
||||
this.Worlds.delete(socket.roomId);
|
||||
this.rooms.delete(socket.roomId);
|
||||
debug('Room %s is empty. Deleting.', socket.roomId);
|
||||
}
|
||||
} else {
|
||||
@ -337,19 +344,23 @@ export class SocketManager implements ZoneEventListener {
|
||||
|
||||
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
|
||||
//check and create new world for a room
|
||||
let world = this.Worlds.get(roomId)
|
||||
let world = this.rooms.get(roomId)
|
||||
if(world === undefined){
|
||||
world = new PusherRoom(roomId, this);
|
||||
if (!world.anonymous) {
|
||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
||||
world.tags = data.tags
|
||||
world.policyType = Number(data.policy_type)
|
||||
if (!world.public) {
|
||||
await this.updateRoomWithAdminData(world);
|
||||
}
|
||||
this.Worlds.set(roomId, world);
|
||||
this.rooms.set(roomId, world);
|
||||
}
|
||||
return Promise.resolve(world)
|
||||
}
|
||||
|
||||
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
|
||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
||||
world.tags = data.tags;
|
||||
world.policyType = Number(data.policy_type);
|
||||
}
|
||||
|
||||
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
||||
const pusherToBackMessage = new PusherToBackMessage();
|
||||
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
|
||||
@ -358,7 +369,7 @@ export class SocketManager implements ZoneEventListener {
|
||||
}
|
||||
|
||||
public getWorlds(): Map<string, PusherRoom> {
|
||||
return this.Worlds;
|
||||
return this.rooms;
|
||||
}
|
||||
|
||||
searchClientByUuid(uuid: string): ExSocketInterface | null {
|
||||
@ -533,6 +544,23 @@ export class SocketManager implements ZoneEventListener {
|
||||
|
||||
emitInBatch(listener, subMessage);
|
||||
}
|
||||
|
||||
public emitWorldFullMessage(client: WebSocket) {
|
||||
const errorMessage = new WorldFullMessage();
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setWorldfullmessage(errorMessage);
|
||||
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
}
|
||||
|
||||
private refreshRoomData(roomId: string, versionNumber: number): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
|
||||
if (!room || !room.needsUpdate(versionNumber)) return;
|
||||
|
||||
this.updateRoomWithAdminData(room);
|
||||
}
|
||||
}
|
||||
|
||||
export const socketManager = new SocketManager();
|
||||
|
@ -3032,9 +3032,9 @@ xtend@^4.0.0:
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||
|
||||
yallist@^3.0.0, yallist@^3.0.3:
|
||||
version "3.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user