Merge branch 'develop' of github.com:thecodingmachine/workadventure into iframe_api

This commit is contained in:
David Négrier 2021-03-12 18:02:02 +01:00
commit 086b88b09a
22 changed files with 302 additions and 436 deletions

View File

@ -3,7 +3,6 @@ const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS)
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false; const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
const ADMIN_API_URL = process.env.ADMIN_API_URL || ''; const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken'; const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600;
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_ISS = process.env.JITSI_ISS || ''; const JITSI_ISS = process.env.JITSI_ISS || '';
@ -19,7 +18,6 @@ export {
ADMIN_API_TOKEN, ADMIN_API_TOKEN,
HTTP_PORT, HTTP_PORT,
GRPC_PORT, GRPC_PORT,
MAX_USERS_PER_ROOM,
GROUP_RADIUS, GROUP_RADIUS,
ALLOW_ARTILLERY, ALLOW_ARTILLERY,
CPU_OVERHEAT_THRESHOLD, CPU_OVERHEAT_THRESHOLD,

View File

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

View File

@ -176,7 +176,7 @@ const roomManager: IRoomManagerServer = {
}, },
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void { ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
// FIXME Work in progress // FIXME Work in progress
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), 'foo bar TODO change this'); socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage());
callback(null, new EmptyMessage()); callback(null, new EmptyMessage());
}, },

View File

@ -690,7 +690,7 @@ export class SocketManager {
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void { public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
const room = this.rooms.get(roomId); const room = this.rooms.get(roomId);
if (!room) { if (!room) {
console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
return; return;
} }
@ -704,10 +704,10 @@ export class SocketManager {
sendUserMessage.setMessage(message); sendUserMessage.setMessage(message);
sendUserMessage.setType('ban'); //todo: is the type correct? sendUserMessage.setType('ban'); //todo: is the type correct?
const subToPusherMessage = new SubToPusherMessage(); const serverToClientMessage = new ServerToClientMessage();
subToPusherMessage.setSendusermessage(sendUserMessage); serverToClientMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(subToPusherMessage); recipient.socket.write(serverToClientMessage);
} }
public banUser(roomId: string, recipientUuid: string, message: string): void { public banUser(roomId: string, recipientUuid: string, message: string): void {
@ -726,16 +726,15 @@ export class SocketManager {
// Let's leave the room now. // Let's leave the room now.
room.leave(recipient); room.leave(recipient);
const sendUserMessage = new SendUserMessage(); const banUserMessage = new BanUserMessage();
sendUserMessage.setMessage(message); banUserMessage.setMessage(message);
sendUserMessage.setType('banned'); banUserMessage.setType('banned');
const subToPusherMessage = new SubToPusherMessage(); const serverToClientMessage = new ServerToClientMessage();
subToPusherMessage.setSendusermessage(sendUserMessage); serverToClientMessage.setBanusermessage(banUserMessage);
recipient.socket.write(subToPusherMessage);
// Let's close the connection when the user is banned. // Let's close the connection when the user is banned.
recipient.socket.write(serverToClientMessage);
recipient.socket.end(); recipient.socket.end();
} }

View File

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

View File

@ -1,5 +1,5 @@
import {Subject} from "rxjs"; import {Subject} from "rxjs";
import {SendUserMessage} from "../Messages/generated/messages_pb"; import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
export enum AdminMessageEventTypes { export enum AdminMessageEventTypes {
admin = 'message', admin = 'message',
@ -23,7 +23,7 @@ class AdminMessagesService {
this.messageStream.subscribe((event) => console.log('message', event)) this.messageStream.subscribe((event) => console.log('message', event))
} }
onSendusermessage(message: SendUserMessage) { onSendusermessage(message: SendUserMessage|BanUserMessage) {
this._messageStream.next({ this._messageStream.next({
type: message.getType() as unknown as AdminMessageEventTypes, type: message.getType() as unknown as AdminMessageEventTypes,
text: message.getMessage(), text: message.getMessage(),

View File

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

View File

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

View File

@ -27,7 +27,7 @@ import {
SendJitsiJwtMessage, SendJitsiJwtMessage,
CharacterLayerMessage, CharacterLayerMessage,
PingMessage, PingMessage,
SendUserMessage SendUserMessage, BanUserMessage
} from "../Messages/generated/messages_pb" } from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -43,6 +43,7 @@ import {
} from "./ConnexionModels"; } from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures"; import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
import {adminMessagesService} from "./AdminMessagesService"; import {adminMessagesService} from "./AdminMessagesService";
import {connectionManager, ConnexionMessageEventTypes} from "./ConnectionManager";
const manualPingDelay = 20000; const manualPingDelay = 20000;
@ -101,7 +102,7 @@ export class RoomConnection implements RoomConnection {
} }
// If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry. // If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry.
if (this.userId === null) { if (this.userId === null && !this.closed) {
this.dispatch(EventMessage.CONNECTING_ERROR, event); this.dispatch(EventMessage.CONNECTING_ERROR, event);
} }
}); });
@ -156,7 +157,8 @@ export class RoomConnection implements RoomConnection {
} as RoomJoinedMessageInterface } as RoomJoinedMessageInterface
}); });
} else if (message.hasErrormessage()) { } else if (message.hasErrormessage()) {
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage()); connectionManager._connexionMessageStream.next({type: ConnexionMessageEventTypes.worldFull}); //todo: generalize this behavior to all messages
this.closed = true;
} else if (message.hasWebrtcsignaltoclientmessage()) { } else if (message.hasWebrtcsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage()); this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) { } else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
@ -175,6 +177,8 @@ export class RoomConnection implements RoomConnection {
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage()); this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
} else if (message.hasSendusermessage()) { } else if (message.hasSendusermessage()) {
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage); adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
} else if (message.hasBanusermessage()) {
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
} else { } else {
throw new Error('Unknown message received'); throw new Error('Unknown message received');
} }

View File

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

View File

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

View File

@ -3,27 +3,17 @@ import {
GroupCreatedUpdatedMessageInterface, GroupCreatedUpdatedMessageInterface,
MessageUserJoined, MessageUserJoined,
MessageUserMovedInterface, MessageUserMovedInterface,
MessageUserPositionInterface, OnConnectInterface, MessageUserPositionInterface,
OnConnectInterface,
PointInterface, PointInterface,
PositionInterface, PositionInterface,
RoomJoinedMessageInterface RoomJoinedMessageInterface
} from "../../Connexion/ConnexionModels"; } from "../../Connexion/ConnexionModels";
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
import { import {DEBUG_MODE, JITSI_PRIVATE_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
DEBUG_MODE, import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
JITSI_PRIVATE_MODE,
POSITION_DELAY,
RESOLUTION,
ZOOM_LEVEL
} from "../../Enum/EnvironmentVariable";
import {
ITiledMap,
ITiledMapLayer,
ITiledMapLayerProperty, ITiledMapObject,
ITiledTileSet
} from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface"; import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationNames} from "../Player/Animation"; import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement"; import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer"; import {RemotePlayer} from "../Entity/RemotePlayer";
@ -41,11 +31,6 @@ import {
TRIGGER_WEBSITE_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES WEBSITE_MESSAGE_PROPERTIES
} from "../../WebRtc/LayoutManager"; } from "../../WebRtc/LayoutManager";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {GameMap} from "./GameMap"; import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager"; import {mediaManager} from "../../WebRtc/MediaManager";
@ -54,7 +39,7 @@ import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager"; import {UserInputManager} from "../UserInput/UserInputManager";
import {UserMovedMessage} from "../../Messages/generated/messages_pb"; import {UserMovedMessage} from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils"; import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager"; import {connectionManager, ConnexionMessageEvent, ConnexionMessageEventTypes} from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection"; import {RoomConnection} from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
import {userMessageManager} from "../../Administration/UserMessageManager"; import {userMessageManager} from "../../Administration/UserMessageManager";
@ -76,6 +61,12 @@ import {iframeListener} from "../../Api/IframeListener";
import DOMElement = Phaser.GameObjects.DOMElement; import DOMElement = Phaser.GameObjects.DOMElement;
import Tween = Phaser.Tweens.Tween; import Tween = Phaser.Tweens.Tween;
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {Subscription} from "rxjs";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface|null, initPosition: PointInterface|null,
@ -161,11 +152,12 @@ export class GameScene extends ResizableScene implements CenterListener {
// The item that can be selected by pressing the space key. // The item that can be selected by pressing the space key.
private outlinedItem: ActionableItem|null = null; private outlinedItem: ActionableItem|null = null;
public userInputManager!: UserInputManager; public userInputManager!: UserInputManager;
private isReconnecting: boolean = false; private isReconnecting: boolean|undefined = undefined;
private startLayerName!: string | null; private startLayerName!: string | null;
private openChatIcon!: OpenChatIcon; private openChatIcon!: OpenChatIcon;
private playerName!: string; private playerName!: string;
private characterLayers!: string[]; private characterLayers!: string[];
private messageSubscription: Subscription|null = null;
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>(); private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
@ -299,25 +291,6 @@ export class GameScene extends ResizableScene implements CenterListener {
} }
}); });
}); });
// import(/* webpackIgnore: true */ scriptUrl).then(result => {
//
// result.default.preload(this.load);
//
// this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends.
// this.load.on('complete', () => {
// // FIXME: the factory might fail because the resources might not be loaded yet...
// // We would need to add a loader ended event in addition to the createPromise
// this.createPromise.then(() => {
// result.default.create(this);
//
// for (let object of objectsOfType) {
// // TODO: we should pass here a factory to create sprites (maybe?)
// let objectSprite = result.default.factory(this, object);
// }
// });
// });
// });
} }
// Now, let's load the script, if any // Now, let's load the script, if any
@ -343,6 +316,8 @@ export class GameScene extends ResizableScene implements CenterListener {
urlManager.pushRoomIdToUrl(this.room); urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl(); this.startLayerName = urlManager.getStartLayerNameFromUrl();
this.messageSubscription = connectionManager._connexionMessageStream.subscribe((event) => this.onConnexionMessage(event))
const playerName = gameManager.getPlayerName(); const playerName = gameManager.getPlayerName();
if (!playerName) { if (!playerName) {
throw 'playerName is not set'; throw 'playerName is not set';
@ -413,13 +388,13 @@ export class GameScene extends ResizableScene implements CenterListener {
this.scene.launch(ReconnectingSceneName); this.scene.launch(ReconnectingSceneName);
}, 0); }, 0);
} else if (this.connection === undefined) { } else if (this.connection === undefined) {
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking // Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout(() => { setTimeout(() => {
if (this.connection === undefined) { if (this.connection === undefined) {
this.scene.sleep(); this.scene.sleep();
this.scene.launch(ReconnectingSceneName); this.scene.launch(ReconnectingSceneName);
} }
}, 500); }, 1000);
} }
this.createPromiseResolve(); this.createPromiseResolve();
@ -582,8 +557,6 @@ export class GameScene extends ResizableScene implements CenterListener {
//init user position and play trigger to check layers properties //init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
return this.connection;
}); });
} }
@ -621,29 +594,6 @@ export class GameScene extends ResizableScene implements CenterListener {
this.circleRedTexture.refresh(); this.circleRedTexture.refresh();
} }
private playAudio(url: string|number|boolean|undefined, loop=false): void {
if (url === undefined) {
audioManager.unloadAudio();
} else {
const audioPath = url as string;
let realAudioPath = '';
if (audioPath.indexOf('://') > 0) {
// remote file or stream
realAudioPath = audioPath;
} else {
// local file, include it relative to map directory
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
realAudioPath = mapDirUrl + '/' + url;
}
audioManager.loadAudio(realAudioPath);
if (loop) {
audioManager.loop();
}
}
}
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) { private safeParseJSONstring(jsonString: string|undefined, propertyName: string) {
try { try {
@ -724,11 +674,11 @@ export class GameScene extends ResizableScene implements CenterListener {
} }
}); });
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => { this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
this.playAudio(newValue); newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
}); });
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => { this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
this.playAudio(newValue, true); newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
}); });
this.gameMap.onPropertyChange('zone', (newValue, oldValue) => { this.gameMap.onPropertyChange('zone', (newValue, oldValue) => {
@ -797,6 +747,10 @@ ${escapedMessage}
}); });
} }
private getMapDirUrl(): string {
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
}
private onMapExit(exitKey: string) { private onMapExit(exitKey: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance); const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey); if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
@ -828,14 +782,11 @@ ${escapedMessage}
} }
this.stopJitsi(); this.stopJitsi();
this.playAudio(undefined); audioManager.unloadAudio();
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map. // We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
if(this.connection) { this.connection?.closeConnection();
this.connection.closeConnection(); this.simplePeer?.unregister();
} this.messageSubscription?.unsubscribe();
if(this.simplePeer) {
this.simplePeer.unregister();
}
} }
private removeAllRemotePlayers(): void { private removeAllRemotePlayers(): void {
@ -1009,7 +960,7 @@ ${escapedMessage}
this.startY, this.startY,
this.playerName, this.playerName,
texturesPromise, texturesPromise,
PlayerAnimationNames.WalkDown, PlayerAnimationDirections.Down,
false, false,
this.userInputManager this.userInputManager
); );
@ -1058,16 +1009,16 @@ ${escapedMessage}
let x = event.x; let x = event.x;
let y = event.y; let y = event.y;
switch (event.direction) { switch (event.direction) {
case PlayerAnimationNames.WalkUp: case PlayerAnimationDirections.Up:
y -= 32; y -= 32;
break; break;
case PlayerAnimationNames.WalkDown: case PlayerAnimationDirections.Down:
y += 32; y += 32;
break; break;
case PlayerAnimationNames.WalkLeft: case PlayerAnimationDirections.Left:
x -= 32; x -= 32;
break; break;
case PlayerAnimationNames.WalkRight: case PlayerAnimationDirections.Right:
x += 32; x += 32;
break; break;
default: default:
@ -1203,7 +1154,7 @@ ${escapedMessage}
addPlayerData.position.y, addPlayerData.position.y,
addPlayerData.name, addPlayerData.name,
texturesPromise, texturesPromise,
addPlayerData.position.direction, addPlayerData.position.direction as PlayerAnimationDirections,
addPlayerData.position.moving addPlayerData.position.moving
); );
this.MapPlayers.add(player); this.MapPlayers.add(player);
@ -1362,21 +1313,34 @@ ${escapedMessage}
} }
public stopJitsi(): void { public stopJitsi(): void {
this.connection.setSilent(false); this.connection?.setSilent(false);
jitsiFactory.stop(); jitsiFactory.stop();
mediaManager.showGameOverlay(); mediaManager.showGameOverlay();
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
} }
//todo: into onConnexionMessage
private bannedUser(){ private bannedUser(){
this.cleanupClosingScene(); this.cleanupClosingScene();
this.userInputManager.clearAllKeys(); this.userInputManager.clearAllKeys();
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
title: 'Banned', title: 'Banned',
subTitle: 'You was banned of WorkAdventure', subTitle: 'You were banned from WorkAdventure',
message: 'If you want more information, you can contact us: workadventure@thecodingmachine.com' message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
}); });
} }
private onConnexionMessage(event: ConnexionMessageEvent) {
if (event.type === ConnexionMessageEventTypes.worldFull) {
this.cleanupClosingScene();
this.scene.stop(ReconnectingSceneName);
this.userInputManager.clearAllKeys();
this.scene.start(ErrorSceneName, {
title: 'Connection rejected',
subTitle: 'The world you are trying to join is full. Try again later.',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
});
}
}
} }

View File

@ -104,8 +104,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
gamePError.innerText = ''; gamePError.innerText = '';
gamePError.style.display = 'none'; gamePError.style.display = 'none';
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement; const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
const gameIdUserReported = this.getChildByID('idUserReported') as HTMLInputElement; if(!gameTextArea || !gameTextArea.value){
if(!gameTextArea || !gameTextArea.value ){
gamePError.innerText = 'Report message cannot to be empty.'; gamePError.innerText = 'Report message cannot to be empty.';
gamePError.style.display = 'block'; gamePError.style.display = 'block';
return; return;
@ -116,4 +115,4 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
); );
this.close(); this.close();
} }
} }

View File

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

View File

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

View File

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

View File

@ -350,6 +350,7 @@ message AdminMessage {
string message = 1; string message = 1;
string recipientUuid = 2; string recipientUuid = 2;
string roomId = 3; string roomId = 3;
string type = 4;
} }
// A message sent by an administrator to everyone in a specific room // A message sent by an administrator to everyone in a specific room
@ -366,6 +367,8 @@ message AdminGlobalMessage {
message BanMessage { message BanMessage {
string recipientUuid = 1; string recipientUuid = 1;
string roomId = 2; string roomId = 2;
string type = 3;
string message = 4;
} }
message EmptyMessage { message EmptyMessage {

View File

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

View File

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

View File

@ -58,12 +58,12 @@ class AdminApi {
return res.data; return res.data;
} }
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> { async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!'); return Promise.reject('No admin backoffice set!');
} }
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid, const res = await Axios.get(ADMIN_API_URL+'/api/room/access',
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } { params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
) )
return res.data; return res.data;
} }

View File

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

View File

@ -4,7 +4,6 @@ import {
GroupDeleteMessage, GroupDeleteMessage,
ItemEventMessage, ItemEventMessage,
PlayGlobalMessage, PlayGlobalMessage,
PositionMessage,
RoomJoinedMessage, RoomJoinedMessage,
ServerToClientMessage, ServerToClientMessage,
SetPlayerDetailsMessage, SetPlayerDetailsMessage,
@ -23,23 +22,17 @@ import {
AdminPusherToBackMessage, AdminPusherToBackMessage,
ServerToAdminClientMessage, ServerToAdminClientMessage,
SendUserMessage, SendUserMessage,
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {cpuTracker} from "./CpuTracker"; import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture} from "./AdminApi"; import {adminApi, CharacterTexture} from "./AdminApi";
import Direction = PositionMessage.Direction;
import {emitError, emitInBatch} from "./IoSocketHelpers"; import {emitError, emitInBatch} from "./IoSocketHelpers";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable"; import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter"; import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager"; import {gaugeManager} from "./GaugeManager";
import {apiClientRepository} from "./ApiClientRepository"; import {apiClientRepository} from "./ApiClientRepository";
import {ServiceError} from "grpc";
import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone"; import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone";
import Debug from "debug"; import Debug from "debug";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface"; import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
@ -271,31 +264,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setItemeventmessage(itemEventMessage); pusherToBackMessage.setItemeventmessage(itemEventMessage);
client.backConnection.write(pusherToBackMessage); client.backConnection.write(pusherToBackMessage);
/*const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
try {
const world = this.Worlds.get(ws.roomId);
if (!world) {
console.error("Could not find world with id '", ws.roomId, "'");
return;
}
const subMessage = new SubMessage();
subMessage.setItemeventmessage(itemEventMessage);
// Let's send the event without using the SocketIO room.
for (const user of world.getUsers().values()) {
const client = this.searchClientByIdOrFail(user.id);
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
emitInBatch(client, subMessage);
}
world.setItemState(itemEvent.itemId, itemEvent.state);
} catch (e) {
console.error('An error occurred on "item_event"');
console.error(e);
}*/
} }
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) { async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
@ -317,25 +285,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setWebrtcsignaltoservermessage(data); pusherToBackMessage.setWebrtcsignaltoservermessage(data);
socket.backConnection.write(pusherToBackMessage); socket.backConnection.write(pusherToBackMessage);
//send only at user
/*const client = this.sockets.get(data.getReceiverid());
if (client === undefined) {
console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(socket.userId);
webrtcSignalToClient.setSignal(data.getSignal());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
} }
emitScreenSharing(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void { emitScreenSharing(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void {
@ -343,24 +292,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setWebrtcscreensharingsignaltoservermessage(data); pusherToBackMessage.setWebrtcscreensharingsignaltoservermessage(data);
socket.backConnection.write(pusherToBackMessage); socket.backConnection.write(pusherToBackMessage);
//send only at user
/*const client = this.sockets.get(data.getReceiverid());
if (client === undefined) {
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(socket.userId);
webrtcSignalToClient.setSignal(data.getSignal());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
} }
private searchClientByIdOrFail(userId: number): ExSocketInterface { private searchClientByIdOrFail(userId: number): ExSocketInterface {
@ -408,17 +339,7 @@ export class SocketManager implements ZoneEventListener {
//check and create new world for a room //check and create new world for a room
let world = this.Worlds.get(roomId) let world = this.Worlds.get(roomId)
if(world === undefined){ if(world === undefined){
world = new PusherRoom( world = new PusherRoom(roomId, this);
roomId,
this
/* (user: User, group: Group) => this.joinWebRtcRoom(user, group),
(user: User, group: Group) => this.disConnectedUser(user, group),
MINIMUM_DISTANCE,
GROUP_RADIUS,
(thing: Movable, listener: User) => this.onRoomEnter(thing, listener),
(thing: Movable, position:PositionInterface, listener:User) => this.onClientMove(thing, position, listener),
(thing: Movable, listener:User) => this.onClientLeave(thing, listener)*/
);
if (!world.anonymous) { if (!world.anonymous) {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug) const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
world.tags = data.tags world.tags = data.tags
@ -429,60 +350,6 @@ export class SocketManager implements ZoneEventListener {
return Promise.resolve(world) return Promise.resolve(world)
} }
/* private joinRoom(client : ExSocketInterface, position: PointInterface): PusherRoom {
const roomId = client.roomId;
client.position = position;
const world = this.Worlds.get(roomId)
if(world === undefined){
throw new Error('Could not find room for ID: '+client.roomId)
}
// Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(client, group);
});
//join world
world.join(client, client.position);
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
return world;
}
private onClientMove(thing: Movable, position:PositionInterface, listener:User): void {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(clientUser.userId);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}
private onClientLeave(thing: Movable, listener:User) {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
this.emitUserLeftEvent(clientListener, clientUser.userId);
} else if (thing instanceof Group) {
this.emitDeleteGroupEvent(clientListener, thing.getId());
} else {
console.error('Unexpected type for Movable.');
}
}*/
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) { emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
const pusherToBackMessage = new PusherToBackMessage(); const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setPlayglobalmessage(playglobalmessage); pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
@ -505,90 +372,94 @@ export class SocketManager implements ZoneEventListener {
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) { public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
const room = queryJitsiJwtMessage.getJitsiroom(); try {
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead. const room = queryJitsiJwtMessage.getJitsiroom();
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
if (SECRET_JITSI_KEY === '') { if (SECRET_JITSI_KEY === '') {
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.'); throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
}
// Let's see if the current client has
const isAdmin = client.tags.includes(tag);
const jwt = Jwt.sign({
"aud": "jitsi",
"iss": JITSI_ISS,
"sub": JITSI_URL,
"room": room,
"moderator": isAdmin
}, SECRET_JITSI_KEY, {
expiresIn: '1d',
algorithm: "HS256",
header:
{
"alg": "HS256",
"typ": "JWT"
}
});
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
sendJitsiJwtMessage.setJitsiroom(room);
sendJitsiJwtMessage.setJwt(jwt);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendjitsijwtmessage(sendJitsiJwtMessage);
client.send(serverToClientMessage.serializeBinary().buffer, true);
} catch (e) {
console.error('An error occured while generating the Jitsi JWT token: ', e);
} }
}
// Let's see if the current client has public async emitSendUserMessage(userUuid: string, message: string, type: string, roomId: string) {
const isAdmin = client.tags.includes(tag); /*const client = this.searchClientByUuid(userUuid);
if(client) {
const adminMessage = new SendUserMessage();
adminMessage.setMessage(message);
adminMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setSendusermessage(adminMessage);
client.backConnection.write(pusherToBackMessage);
return;
}*/
const jwt = Jwt.sign({ const backConnection = await apiClientRepository.getClient(roomId);
"aud": "jitsi", const backAdminMessage = new AdminMessage();
"iss": JITSI_ISS, backAdminMessage.setMessage(message);
"sub": JITSI_URL, backAdminMessage.setRoomid(roomId);
"room": room, backAdminMessage.setRecipientuuid(userUuid);
"moderator": isAdmin backAdminMessage.setType(type);
}, SECRET_JITSI_KEY, { backConnection.sendAdminMessage(backAdminMessage, (error) => {
expiresIn: '1d', if (error !== null) {
algorithm: "HS256", console.error('Error while sending admin message', error);
header: }
{
"alg": "HS256",
"typ": "JWT"
}
}); });
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
sendJitsiJwtMessage.setJitsiroom(room);
sendJitsiJwtMessage.setJwt(jwt);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendjitsijwtmessage(sendJitsiJwtMessage);
client.send(serverToClientMessage.serializeBinary().buffer, true);
} }
public emitSendUserMessage(userUuid: string, message: string, type: string): void { public async emitBan(userUuid: string, message: string, type: string, roomId: string) {
const client = this.searchClientByUuid(userUuid); /*const client = this.searchClientByUuid(userUuid);
if(!client){ if(client) {
throw Error('client not found'); const banUserMessage = new BanUserMessage();
} banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
return;
}*/
const adminMessage = new SendUserMessage(); const backConnection = await apiClientRepository.getClient(roomId);
adminMessage.setMessage(message); const banMessage = new BanMessage();
adminMessage.setType(type); banMessage.setMessage(message);
const pusherToBackMessage = new PusherToBackMessage(); banMessage.setRoomid(roomId);
pusherToBackMessage.setSendusermessage(adminMessage); banMessage.setRecipientuuid(userUuid);
client.backConnection.write(pusherToBackMessage); banMessage.setType(type);
backConnection.ban(banMessage, (error) => {
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) { if (error !== null) {
console.error('Error while sending admin message', error); console.error('Error while sending admin message', error);
} }
});*/ });
}
public emitBan(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
}
});*/
} }
/** /**