2020-10-15 17:25:16 +02:00
import { GameRoom } from "../Model/GameRoom" ;
2020-12-04 15:58:31 +01:00
import { CharacterLayer } from "_Model/Websocket/CharacterLayer" ;
2020-10-15 17:25:16 +02:00
import {
GroupDeleteMessage ,
GroupUpdateMessage ,
ItemEventMessage ,
2020-10-16 19:13:26 +02:00
ItemStateMessage ,
PlayGlobalMessage ,
2020-10-15 17:25:16 +02:00
PointMessage ,
PositionMessage ,
RoomJoinedMessage ,
ServerToClientMessage ,
SetPlayerDetailsMessage ,
SilentMessage ,
SubMessage ,
ReportPlayerMessage ,
2020-11-13 18:00:22 +01:00
UserJoinedMessage ,
UserLeftMessage ,
2020-10-15 17:25:16 +02:00
UserMovedMessage ,
UserMovesMessage ,
2020-11-13 18:00:22 +01:00
ViewportMessage ,
WebRtcDisconnectMessage ,
2020-10-15 17:25:16 +02:00
WebRtcSignalToClientMessage ,
2020-10-19 19:32:47 +02:00
WebRtcSignalToServerMessage ,
WebRtcStartMessage ,
QueryJitsiJwtMessage ,
SendJitsiJwtMessage ,
2020-11-13 18:00:22 +01:00
SendUserMessage ,
JoinRoomMessage ,
Zone as ProtoZone ,
BatchMessage ,
BatchToPusherMessage ,
SubToPusherMessage ,
2020-12-11 12:23:50 +01:00
UserJoinedZoneMessage , GroupUpdateZoneMessage , GroupLeftZoneMessage , UserLeftZoneMessage , AdminMessage , BanMessage
2020-10-15 17:25:16 +02:00
} from "../Messages/generated/messages_pb" ;
2020-11-13 18:00:22 +01:00
import { User , UserSocket } from "../Model/User" ;
2020-10-15 17:25:16 +02:00
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils" ;
import { Group } from "../Model/Group" ;
import { cpuTracker } from "./CpuTracker" ;
2020-11-13 18:00:22 +01:00
import { ADMIN_API_URL , GROUP_RADIUS , JITSI_ISS , MINIMUM_DISTANCE , SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable" ;
2020-10-15 17:25:16 +02:00
import { Movable } from "../Model/Movable" ;
import { PositionInterface } from "../Model/PositionInterface" ;
2020-11-13 18:00:22 +01:00
import { adminApi , CharacterTexture , FetchMemberDataByUuidResponse } from "./AdminApi" ;
2020-10-16 19:13:26 +02:00
import Jwt from "jsonwebtoken" ;
2020-10-19 12:07:05 +02:00
import { JITSI_URL } from "../Enum/EnvironmentVariable" ;
2020-10-16 14:36:43 +02:00
import { clientEventsEmitter } from "./ClientEventsEmitter" ;
2020-10-30 15:23:50 +01:00
import { gaugeManager } from "./GaugeManager" ;
2020-12-10 17:46:15 +01:00
import { AdminSocket , ZoneSocket } from "../RoomManager" ;
2020-11-13 18:00:22 +01:00
import { Zone } from "_Model/Zone" ;
import Debug from "debug" ;
2020-12-10 17:46:15 +01:00
import { Admin } from "_Model/Admin" ;
2020-11-13 18:00:22 +01:00
const debug = Debug ( 'sockermanager' ) ;
2020-10-16 14:36:43 +02:00
interface AdminSocketRoomsList {
[ index : string ] : number ;
}
interface AdminSocketUsersList {
[ index : string ] : boolean ;
}
export interface AdminSocketData {
rooms : AdminSocketRoomsList ,
users : AdminSocketUsersList ,
}
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
function emitZoneMessage ( subMessage : SubToPusherMessage , socket : ZoneSocket ) : void {
// TODO: should we batch those every 100ms?
const batchMessage = new BatchToPusherMessage ( ) ;
batchMessage . addPayload ( subMessage ) ;
socket . write ( batchMessage ) ;
}
2020-10-20 16:39:23 +02:00
export class SocketManager {
2020-11-13 18:00:22 +01:00
private rooms : Map < string , GameRoom > = new Map < string , GameRoom > ( ) ;
2020-10-15 17:25:16 +02:00
constructor ( ) {
2020-10-30 15:23:50 +01:00
clientEventsEmitter . registerToClientJoin ( ( clientUUid : string , roomId : string ) = > {
gaugeManager . incNbClientPerRoomGauge ( roomId ) ;
2020-10-16 14:36:43 +02:00
} ) ;
2020-10-30 15:23:50 +01:00
clientEventsEmitter . registerToClientLeave ( ( clientUUid : string , roomId : string ) = > {
gaugeManager . decNbClientPerRoomGauge ( roomId ) ;
2020-10-16 14:36:43 +02:00
} ) ;
}
2020-10-20 16:39:23 +02:00
2020-12-10 17:46:15 +01:00
/ * g e t A d m i n S o c k e t D a t a F o r ( r o o m I d : s t r i n g ) : A d m i n S o c k e t D a t a {
2020-10-16 14:36:43 +02:00
const data :AdminSocketData = {
rooms : { } ,
users : { } ,
}
2020-11-13 18:00:22 +01:00
const room = this . rooms . get ( roomId ) ;
2020-10-16 14:36:43 +02:00
if ( room === undefined ) {
return data ;
}
const users = room . getUsers ( ) ;
data . rooms [ roomId ] = users . size ;
users . forEach ( user = > {
data . users [ user . uuid ] = true
} )
return data ;
2020-12-10 17:46:15 +01:00
} * /
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
public async handleJoinRoom ( socket : UserSocket , joinRoomMessage : JoinRoomMessage ) : Promise < { room : GameRoom ; user : User } > {
/ * c o n s t p o s i t i o n M e s s a g e = j o i n R o o m M e s s a g e . g e t P o s i t i o n m e s s a g e ( ) ;
if ( positionMessage === undefined ) {
// TODO: send error message?
throw new Error ( 'Empty pointMessage found in JoinRoomMessage' ) ;
} * /
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
//const position = ProtobufUtils.toPointInterface(positionMessage);
//const viewport = client.viewport;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
//this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
//join new previous room
const { room , user } = await this . joinRoom ( socket , joinRoomMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
//const things = room.setViewport(client, viewport);
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const roomJoinedMessage = new RoomJoinedMessage ( ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
/ * f o r ( c o n s t t h i n g o f t h i n g s ) {
if ( thing instanceof User ) {
const player : ExSocketInterface | undefined = this . sockets . get ( thing . id ) ;
if ( player === undefined ) {
console . warn ( 'Something went wrong. The World contains a user "' + thing . id + "' but this user does not exist in the sockets list!" ) ;
continue ;
}
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const userJoinedMessage = new UserJoinedMessage ( ) ;
userJoinedMessage . setUserid ( thing . id ) ;
userJoinedMessage . setName ( player . name ) ;
userJoinedMessage . setCharacterlayersList ( ProtobufUtils . toCharacterLayerMessages ( player . characterLayers ) ) ;
userJoinedMessage . setPosition ( ProtobufUtils . toPositionMessage ( player . position ) ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
roomJoinedMessage . addUser ( userJoinedMessage ) ;
roomJoinedMessage . setTagList ( joinRoomMessage . getTagList ( ) ) ;
} else if ( thing instanceof Group ) {
const groupUpdateMessage = new GroupUpdateMessage ( ) ;
groupUpdateMessage . setGroupid ( thing . getId ( ) ) ;
groupUpdateMessage . setPosition ( ProtobufUtils . toPointMessage ( thing . getPosition ( ) ) ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
roomJoinedMessage . addGroup ( groupUpdateMessage ) ;
} else {
console . error ( "Unexpected type for Movable returned by setViewport" ) ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
} * /
for ( const [ itemId , item ] of room . getItemsState ( ) . entries ( ) ) {
const itemStateMessage = new ItemStateMessage ( ) ;
itemStateMessage . setItemid ( itemId ) ;
itemStateMessage . setStatejson ( JSON . stringify ( item ) ) ;
roomJoinedMessage . addItem ( itemStateMessage ) ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
roomJoinedMessage . setCurrentuserid ( user . id ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setRoomjoinedmessage ( roomJoinedMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
//user.socket.write(serverToClientMessage);
console . log ( 'SENDING MESSAGE roomJoinedMessage' ) ;
socket . write ( serverToClientMessage ) ;
return {
room ,
user
} ;
/ * c o n s t s e r v e r T o C l i e n t M e s s a g e = n e w S e r v e r T o C l i e n t M e s s a g e ( ) ;
serverToClientMessage . setRoomjoinedmessage ( roomJoinedMessage ) ;
if ( ! client . disconnecting ) {
client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
} * /
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
handleUserMovesMessage ( room : GameRoom , user : User , userMovesMessage : UserMovesMessage ) {
2020-10-15 17:25:16 +02:00
try {
const userMoves = userMovesMessage . toObject ( ) ;
2020-11-13 18:00:22 +01:00
const position = userMovesMessage . getPosition ( ) ;
2020-10-15 17:25:16 +02:00
// If CPU is high, let's drop messages of users moving (we will only dispatch the final position)
if ( cpuTracker . isOverHeating ( ) && userMoves . position ? . moving === true ) {
return ;
}
if ( position === undefined ) {
throw new Error ( 'Position not found in message' ) ;
}
const viewport = userMoves . viewport ;
if ( viewport === undefined ) {
throw new Error ( 'Viewport not found in message' ) ;
}
// sending to all clients in room except sender
2020-11-13 18:00:22 +01:00
/ * c l i e n t . p o s i t i o n = {
2020-10-15 17:25:16 +02:00
x : position.x ,
y : position.y ,
direction ,
moving : position.moving ,
} ;
2020-11-13 18:00:22 +01:00
client . viewport = viewport ; * /
2020-10-15 17:25:16 +02:00
// update position in the world
2020-11-13 18:00:22 +01:00
room . updatePosition ( user , ProtobufUtils . toPointInterface ( position ) ) ;
//room.setViewport(client, client.viewport);
2020-10-15 17:25:16 +02:00
} catch ( e ) {
console . error ( 'An error occurred on "user_position" event' ) ;
console . error ( e ) ;
}
}
// Useless now, will be useful again if we allow editing details in game
2020-11-13 18:00:22 +01:00
/ * h a n d l e S e t P l a y e r D e t a i l s ( c l i e n t : U s e r S o c k e t , p l a y e r D e t a i l s M e s s a g e : S e t P l a y e r D e t a i l s M e s s a g e ) {
2020-10-15 17:25:16 +02:00
const playerDetails = {
name : playerDetailsMessage.getName ( ) ,
characterLayers : playerDetailsMessage.getCharacterlayersList ( )
} ;
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if ( ! isSetPlayerDetailsMessage ( playerDetails ) ) {
emitError ( client , 'Invalid SET_PLAYER_DETAILS message received: ' ) ;
return ;
}
client . name = playerDetails . name ;
2020-10-20 16:39:23 +02:00
client . characterLayers = SocketManager . mergeCharacterLayersAndCustomTextures ( playerDetails . characterLayers , client . textures ) ;
2020-11-13 18:00:22 +01:00
} * /
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
handleSilentMessage ( room : GameRoom , user : User , silentMessage : SilentMessage ) {
2020-10-15 17:25:16 +02:00
try {
2020-11-13 18:00:22 +01:00
room . setSilent ( user , silentMessage . getSilent ( ) ) ;
2020-10-15 17:25:16 +02:00
} catch ( e ) {
console . error ( 'An error occurred on "handleSilentMessage"' ) ;
console . error ( e ) ;
}
}
2020-11-13 18:00:22 +01:00
handleItemEvent ( room : GameRoom , user : User , itemEventMessage : ItemEventMessage ) {
2020-10-15 17:25:16 +02:00
const itemEvent = ProtobufUtils . toItemEvent ( itemEventMessage ) ;
try {
const subMessage = new SubMessage ( ) ;
subMessage . setItemeventmessage ( itemEventMessage ) ;
// Let's send the event without using the SocketIO room.
2020-11-13 18:00:22 +01:00
// TODO: move this in the GameRoom class.
for ( const user of room . getUsers ( ) . values ( ) ) {
user . emitInBatch ( subMessage ) ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
room . setItemState ( itemEvent . itemId , itemEvent . state ) ;
2020-10-15 17:25:16 +02:00
} catch ( e ) {
console . error ( 'An error occurred on "item_event"' ) ;
console . error ( e ) ;
}
}
2020-11-13 18:00:22 +01:00
// TODO: handle this message in pusher
/ * a s y n c h a n d l e R e p o r t M e s s a g e ( c l i e n t : E x S o c k e t I n t e r f a c e , r e p o r t P l a y e r M e s s a g e : R e p o r t P l a y e r M e s s a g e ) {
2020-10-15 17:25:16 +02:00
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 ) ;
}
2020-11-13 18:00:22 +01:00
} * /
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
emitVideo ( room : GameRoom , user : User , data : WebRtcSignalToServerMessage ) : void {
2020-10-15 17:25:16 +02:00
//send only at user
2020-11-13 18:00:22 +01:00
const remoteUser = room . getUsers ( ) . get ( data . getReceiverid ( ) ) ;
if ( remoteUser === undefined ) {
2020-10-15 17:25:16 +02:00
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 ( ) ;
2020-11-13 18:00:22 +01:00
webrtcSignalToClient . setUserid ( user . id ) ;
2020-10-15 17:25:16 +02:00
webrtcSignalToClient . setSignal ( data . getSignal ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setWebrtcsignaltoclientmessage ( webrtcSignalToClient ) ;
2020-11-13 18:00:22 +01:00
//if (!client.disconnecting) {
remoteUser . socket . write ( serverToClientMessage ) ;
//}
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
emitScreenSharing ( room : GameRoom , user : User , data : WebRtcSignalToServerMessage ) : void {
2020-10-15 17:25:16 +02:00
//send only at user
2020-11-13 18:00:22 +01:00
const remoteUser = room . getUsers ( ) . get ( data . getReceiverid ( ) ) ;
if ( remoteUser === undefined ) {
2020-10-15 17:25:16 +02:00
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 ( ) ;
2020-11-13 18:00:22 +01:00
webrtcSignalToClient . setUserid ( user . id ) ;
2020-10-15 17:25:16 +02:00
webrtcSignalToClient . setSignal ( data . getSignal ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setWebrtcscreensharingsignaltoclientmessage ( webrtcSignalToClient ) ;
2020-11-13 18:00:22 +01:00
//if (!client.disconnecting) {
remoteUser . socket . write ( serverToClientMessage ) ;
//}
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
leaveRoom ( room : GameRoom , user : User ) {
2020-10-15 17:25:16 +02:00
// leave previous room and world
2020-11-13 18:00:22 +01:00
try {
//user leave previous world
room . leave ( user ) ;
if ( room . isEmpty ( ) ) {
this . rooms . delete ( room . roomId ) ;
debug ( 'Room is empty. Deleting room "%s"' , room . roomId ) ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
} finally {
//delete Client.roomId;
//this.sockets.delete(Client.userId);
clientEventsEmitter . emitClientLeave ( user . uuid , room . roomId ) ;
console . log ( 'A user left' ) ;
2020-10-15 17:25:16 +02:00
}
}
async getOrCreateRoom ( roomId : string ) : Promise < GameRoom > {
//check and create new world for a room
2020-11-13 18:00:22 +01:00
let world = this . rooms . get ( roomId )
2020-10-15 17:25:16 +02:00
if ( world === undefined ) {
world = new GameRoom (
roomId ,
( user : User , group : Group ) = > this . joinWebRtcRoom ( user , group ) ,
( user : User , group : Group ) = > this . disConnectedUser ( user , group ) ,
MINIMUM_DISTANCE ,
GROUP_RADIUS ,
2020-11-13 18:00:22 +01:00
( thing : Movable , fromZone : Zone | null , listener : ZoneSocket ) = > this . onZoneEnter ( thing , fromZone , listener ) ,
( thing : Movable , position :PositionInterface , listener : ZoneSocket ) = > this . onClientMove ( thing , position , listener ) ,
( thing : Movable , newZone : Zone | null , listener : ZoneSocket ) = > this . onClientLeave ( thing , newZone , listener )
2020-10-15 17:25:16 +02:00
) ;
if ( ! world . anonymous ) {
const data = await adminApi . fetchMapDetails ( world . organizationSlug , world . worldSlug , world . roomSlug )
world . tags = data . tags
world . policyType = Number ( data . policy_type )
}
2020-11-13 18:00:22 +01:00
this . rooms . set ( roomId , world ) ;
2020-10-15 17:25:16 +02:00
}
return Promise . resolve ( world )
}
2020-11-13 18:00:22 +01:00
private async joinRoom ( socket : UserSocket , joinRoomMessage : JoinRoomMessage ) : Promise < { room : GameRoom ; user : User } > {
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const roomId = joinRoomMessage . getRoomid ( ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const world = await socketManager . getOrCreateRoom ( roomId ) ;
2020-10-15 17:25:16 +02:00
// Dispatch groups position to newly connected user
2020-11-13 18:00:22 +01:00
/ * w o r l d . g e t G r o u p s ( ) . f o r E a c h ( ( g r o u p : G r o u p ) = > {
this . emitCreateUpdateGroupEvent ( socket , group ) ;
} ) ; * /
2020-10-15 17:25:16 +02:00
//join world
2020-11-13 18:00:22 +01:00
const user = world . join ( socket , joinRoomMessage ) ;
2020-12-10 17:46:15 +01:00
2020-11-13 18:00:22 +01:00
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 } ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
private onZoneEnter ( thing : Movable , fromZone : Zone | null , listener : ZoneSocket ) {
2020-10-15 17:25:16 +02:00
if ( thing instanceof User ) {
2020-11-13 18:00:22 +01:00
const userJoinedZoneMessage = new UserJoinedZoneMessage ( ) ;
if ( ! Number . isInteger ( thing . id ) ) {
throw new Error ( 'clientUser.userId is not an integer ' + thing . id ) ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
userJoinedZoneMessage . setUserid ( thing . id ) ;
userJoinedZoneMessage . setName ( thing . name ) ;
userJoinedZoneMessage . setCharacterlayersList ( ProtobufUtils . toCharacterLayerMessages ( thing . characterLayers ) ) ;
userJoinedZoneMessage . setPosition ( ProtobufUtils . toPositionMessage ( thing . getPosition ( ) ) ) ;
userJoinedZoneMessage . setFromzone ( this . toProtoZone ( fromZone ) ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setUserjoinedzonemessage ( userJoinedZoneMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
emitZoneMessage ( subMessage , listener ) ;
//listener.emitInBatch(subMessage);
2020-10-15 17:25:16 +02:00
} else if ( thing instanceof Group ) {
2020-11-13 18:00:22 +01:00
this . emitCreateUpdateGroupEvent ( listener , fromZone , thing ) ;
2020-10-15 17:25:16 +02:00
} else {
console . error ( 'Unexpected type for Movable.' ) ;
}
}
2020-11-13 18:00:22 +01:00
private onClientMove ( thing : Movable , position :PositionInterface , listener : ZoneSocket ) : void {
2020-10-15 17:25:16 +02:00
if ( thing instanceof User ) {
const userMovedMessage = new UserMovedMessage ( ) ;
2020-11-13 18:00:22 +01:00
userMovedMessage . setUserid ( thing . id ) ;
userMovedMessage . setPosition ( ProtobufUtils . toPositionMessage ( thing . getPosition ( ) ) ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const subMessage = new SubToPusherMessage ( ) ;
2020-10-15 17:25:16 +02:00
subMessage . setUsermovedmessage ( userMovedMessage ) ;
2020-11-13 18:00:22 +01:00
emitZoneMessage ( subMessage , listener ) ;
//listener.emitInBatch(subMessage);
2020-10-15 17:25:16 +02:00
//console.log("Sending USER_MOVED event");
} else if ( thing instanceof Group ) {
2020-11-13 18:00:22 +01:00
this . emitCreateUpdateGroupEvent ( listener , null , thing ) ;
2020-10-15 17:25:16 +02:00
} else {
console . error ( 'Unexpected type for Movable.' ) ;
}
}
2020-11-13 18:00:22 +01:00
private onClientLeave ( thing : Movable , newZone : Zone | null , listener : ZoneSocket ) {
2020-10-15 17:25:16 +02:00
if ( thing instanceof User ) {
2020-11-13 18:00:22 +01:00
this . emitUserLeftEvent ( listener , thing . id , newZone ) ;
2020-10-15 17:25:16 +02:00
} else if ( thing instanceof Group ) {
2020-11-13 18:00:22 +01:00
this . emitDeleteGroupEvent ( listener , thing . getId ( ) , newZone ) ;
2020-10-15 17:25:16 +02:00
} else {
console . error ( 'Unexpected type for Movable.' ) ;
}
}
2020-11-13 18:00:22 +01:00
private emitCreateUpdateGroupEvent ( client : ZoneSocket , fromZone : Zone | null , group : Group ) : void {
2020-10-15 17:25:16 +02:00
const position = group . getPosition ( ) ;
const pointMessage = new PointMessage ( ) ;
pointMessage . setX ( Math . floor ( position . x ) ) ;
pointMessage . setY ( Math . floor ( position . y ) ) ;
2020-11-13 18:00:22 +01:00
const groupUpdateMessage = new GroupUpdateZoneMessage ( ) ;
2020-10-15 17:25:16 +02:00
groupUpdateMessage . setGroupid ( group . getId ( ) ) ;
groupUpdateMessage . setPosition ( pointMessage ) ;
2020-10-21 17:22:17 +02:00
groupUpdateMessage . setGroupsize ( group . getSize ) ;
2020-11-13 18:00:22 +01:00
groupUpdateMessage . setFromzone ( this . toProtoZone ( fromZone ) ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setGroupupdatezonemessage ( groupUpdateMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
emitZoneMessage ( subMessage , client ) ;
//client.emitInBatch(subMessage);
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
private emitDeleteGroupEvent ( client : ZoneSocket , groupId : number , newZone : Zone | null ) : void {
const groupDeleteMessage = new GroupLeftZoneMessage ( ) ;
2020-10-15 17:25:16 +02:00
groupDeleteMessage . setGroupid ( groupId ) ;
2020-11-13 18:00:22 +01:00
groupDeleteMessage . setTozone ( this . toProtoZone ( newZone ) ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setGroupleftzonemessage ( groupDeleteMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
emitZoneMessage ( subMessage , client ) ;
//user.emitInBatch(subMessage);
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
private emitUserLeftEvent ( client : ZoneSocket , userId : number , newZone : Zone | null ) : void {
const userLeftMessage = new UserLeftZoneMessage ( ) ;
2020-10-15 17:25:16 +02:00
userLeftMessage . setUserid ( userId ) ;
2020-11-13 18:00:22 +01:00
userLeftMessage . setTozone ( this . toProtoZone ( newZone ) ) ;
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setUserleftzonemessage ( userLeftMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
emitZoneMessage ( subMessage , client ) ;
}
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
private toProtoZone ( zone : Zone | null ) : ProtoZone | undefined {
if ( zone !== null ) {
const zoneMessage = new ProtoZone ( ) ;
zoneMessage . setX ( zone . x ) ;
zoneMessage . setY ( zone . y ) ;
return zoneMessage ;
}
return undefined ;
2020-10-15 17:25:16 +02:00
}
private joinWebRtcRoom ( user : User , group : Group ) {
/ * c o n s t r o o m I d : s t r i n g = " w e b r t c r o o m " + g r o u p . g e t I d ( ) ;
if ( user . socket . webRtcRoomId === roomId ) {
return ;
} * /
for ( const otherUser of group . getUsers ( ) ) {
if ( user === otherUser ) {
continue ;
}
// Let's send 2 messages: one to the user joining the group and one to the other user
const webrtcStartMessage1 = new WebRtcStartMessage ( ) ;
webrtcStartMessage1 . setUserid ( otherUser . id ) ;
2020-11-13 18:00:22 +01:00
webrtcStartMessage1 . setName ( otherUser . name ) ;
2020-10-15 17:25:16 +02:00
webrtcStartMessage1 . setInitiator ( true ) ;
const serverToClientMessage1 = new ServerToClientMessage ( ) ;
serverToClientMessage1 . setWebrtcstartmessage ( webrtcStartMessage1 ) ;
2020-11-13 18:00:22 +01:00
//if (!user.socket.disconnecting) {
user . socket . write ( serverToClientMessage1 ) ;
2020-10-15 17:25:16 +02:00
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
2020-11-13 18:00:22 +01:00
//}
2020-10-15 17:25:16 +02:00
const webrtcStartMessage2 = new WebRtcStartMessage ( ) ;
webrtcStartMessage2 . setUserid ( user . id ) ;
2020-11-13 18:00:22 +01:00
webrtcStartMessage2 . setName ( user . name ) ;
2020-10-15 17:25:16 +02:00
webrtcStartMessage2 . setInitiator ( false ) ;
const serverToClientMessage2 = new ServerToClientMessage ( ) ;
serverToClientMessage2 . setWebrtcstartmessage ( webrtcStartMessage2 ) ;
2020-11-13 18:00:22 +01:00
//if (!otherUser.socket.disconnecting) {
otherUser . socket . write ( serverToClientMessage2 ) ;
2020-10-15 17:25:16 +02:00
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
2020-11-13 18:00:22 +01:00
//}
2020-10-15 17:25:16 +02:00
}
}
//disconnect user
private disConnectedUser ( user : User , group : Group ) {
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
// which will be shut for the other player).
// However! In the rare case where the WebRTC connection is not yet established, if we close the connection on one of the player,
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing).
// So we also send the disconnect event to the other player.
for ( const otherUser of group . getUsers ( ) ) {
if ( user === otherUser ) {
continue ;
}
const webrtcDisconnectMessage1 = new WebRtcDisconnectMessage ( ) ;
webrtcDisconnectMessage1 . setUserid ( user . id ) ;
const serverToClientMessage1 = new ServerToClientMessage ( ) ;
serverToClientMessage1 . setWebrtcdisconnectmessage ( webrtcDisconnectMessage1 ) ;
2020-11-13 18:00:22 +01:00
//if (!otherUser.socket.disconnecting) {
otherUser . socket . write ( serverToClientMessage1 ) ;
//}
2020-10-15 17:25:16 +02:00
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage ( ) ;
webrtcDisconnectMessage2 . setUserid ( otherUser . id ) ;
const serverToClientMessage2 = new ServerToClientMessage ( ) ;
serverToClientMessage2 . setWebrtcdisconnectmessage ( webrtcDisconnectMessage2 ) ;
2020-11-13 18:00:22 +01:00
//if (!user.socket.disconnecting) {
user . socket . write ( serverToClientMessage2 ) ;
//}
2020-10-15 17:25:16 +02:00
}
}
2020-11-13 18:00:22 +01:00
emitPlayGlobalMessage ( room : GameRoom , playGlobalMessage : PlayGlobalMessage ) {
2020-10-15 17:25:16 +02:00
try {
const serverToClientMessage = new ServerToClientMessage ( ) ;
2020-11-13 18:00:22 +01:00
serverToClientMessage . setPlayglobalmessage ( playGlobalMessage ) ;
2020-10-15 17:25:16 +02:00
2020-11-13 18:00:22 +01:00
for ( const [ id , user ] of room . getUsers ( ) . entries ( ) ) {
user . socket . write ( serverToClientMessage ) ;
2020-10-15 17:25:16 +02:00
}
} catch ( e ) {
console . error ( 'An error occurred on "emitPlayGlobalMessage" event' ) ;
console . error ( e ) ;
}
}
public getWorlds ( ) : Map < string , GameRoom > {
2020-11-13 18:00:22 +01:00
return this . rooms ;
2020-10-15 17:25:16 +02:00
}
/ * *
*
* @param token
* /
2020-11-13 18:00:22 +01:00
/ * s e a r c h C l i e n t B y U u i d ( u u i d : s t r i n g ) : E x S o c k e t I n t e r f a c e | n u l l {
2020-10-15 17:25:16 +02:00
for ( const socket of this . sockets . values ( ) ) {
if ( socket . userUuid === uuid ) {
return socket ;
}
}
return null ;
2020-11-13 18:00:22 +01:00
} * /
2020-10-16 19:13:26 +02:00
2020-11-13 18:00:22 +01:00
public handleQueryJitsiJwtMessage ( user : User , queryJitsiJwtMessage : QueryJitsiJwtMessage ) {
2020-10-16 19:13:26 +02:00
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 === '' ) {
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
2020-11-13 18:00:22 +01:00
const isAdmin = user . tags . includes ( tag ) ;
2020-10-16 19:13:26 +02:00
const jwt = Jwt . sign ( {
"aud" : "jitsi" ,
2020-10-19 12:07:05 +02:00
"iss" : JITSI_ISS ,
"sub" : JITSI_URL ,
"room" : room ,
"moderator" : isAdmin
2020-10-16 19:13:26 +02:00
} , 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 ) ;
2020-11-13 18:00:22 +01:00
user . socket . write ( serverToClientMessage ) ;
2020-10-16 19:13:26 +02:00
}
2020-10-19 19:32:47 +02:00
2020-10-20 16:39:23 +02:00
/ * *
* Merges the characterLayers received from the front ( as an array of string ) with the custom textures from the back .
* /
static mergeCharacterLayersAndCustomTextures ( characterLayers : string [ ] , memberTextures : CharacterTexture [ ] ) : CharacterLayer [ ] {
const characterLayerObjs : CharacterLayer [ ] = [ ] ;
for ( const characterLayer of characterLayers ) {
if ( characterLayer . startsWith ( 'customCharacterTexture' ) ) {
const customCharacterLayerId : number = + characterLayer . substr ( 22 ) ;
for ( const memberTexture of memberTextures ) {
if ( memberTexture . id == customCharacterLayerId ) {
characterLayerObjs . push ( {
name : characterLayer ,
url : memberTexture.url
} )
break ;
}
}
} else {
characterLayerObjs . push ( {
name : characterLayer ,
url : undefined
} )
}
}
return characterLayerObjs ;
}
2020-11-13 18:00:22 +01:00
public addZoneListener ( call : ZoneSocket , roomId : string , x : number , y : number ) : void {
const room = this . rooms . get ( roomId ) ;
if ( ! room ) {
console . error ( "In addZoneListener, could not find room with id '" + roomId + "'" ) ;
return ;
}
const things = room . addZoneListener ( call , x , y ) ;
const batchMessage = new BatchToPusherMessage ( ) ;
for ( const thing of things ) {
if ( thing instanceof User ) {
const userJoinedMessage = new UserJoinedZoneMessage ( ) ;
userJoinedMessage . setUserid ( thing . id ) ;
userJoinedMessage . setName ( thing . name ) ;
userJoinedMessage . setCharacterlayersList ( ProtobufUtils . toCharacterLayerMessages ( thing . characterLayers ) ) ;
userJoinedMessage . setPosition ( ProtobufUtils . toPositionMessage ( thing . getPosition ( ) ) ) ;
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setUserjoinedzonemessage ( userJoinedMessage ) ;
batchMessage . addPayload ( subMessage ) ;
} else if ( thing instanceof Group ) {
const groupUpdateMessage = new GroupUpdateZoneMessage ( ) ;
groupUpdateMessage . setGroupid ( thing . getId ( ) ) ;
groupUpdateMessage . setPosition ( ProtobufUtils . toPointMessage ( thing . getPosition ( ) ) ) ;
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setGroupupdatezonemessage ( groupUpdateMessage ) ;
batchMessage . addPayload ( subMessage ) ;
} else {
console . error ( "Unexpected type for Movable returned by setViewport" ) ;
}
}
call . write ( batchMessage ) ;
}
removeZoneListener ( call : ZoneSocket , roomId : string , x : number , y : number ) {
const room = this . rooms . get ( roomId ) ;
if ( ! room ) {
console . error ( "In removeZoneListener, could not find room with id '" + roomId + "'" ) ;
return ;
}
room . removeZoneListener ( call , x , y ) ;
}
2020-12-10 17:46:15 +01:00
public async handleJoinAdminRoom ( admin : Admin , roomId : string ) : Promise < GameRoom > {
const room = await socketManager . getOrCreateRoom ( roomId ) ;
// Dispatch groups position to newly connected user
/ * w o r l d . g e t G r o u p s ( ) . f o r E a c h ( ( g r o u p : G r o u p ) = > {
this . emitCreateUpdateGroupEvent ( socket , group ) ;
} ) ; * /
room . adminJoin ( admin ) ;
return room ;
}
public leaveAdminRoom ( room : GameRoom , admin : Admin ) {
room . adminLeave ( admin ) ;
if ( room . isEmpty ( ) ) {
this . rooms . delete ( room . roomId ) ;
debug ( 'Room is empty. Deleting room "%s"' , room . roomId ) ;
}
}
2020-12-11 12:23:50 +01:00
public sendAdminMessage ( roomId : string , recipientUuid : string , message : string ) : void {
const room = this . rooms . get ( roomId ) ;
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?" ) ;
return ;
}
const recipient = room . getUserByUuid ( recipientUuid ) ;
if ( recipient === undefined ) {
console . error ( "In sendAdminMessage, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?" ) ;
return ;
}
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setMessage ( message ) ;
sendUserMessage . setType ( 'ban' ) ;
const subToPusherMessage = new SubToPusherMessage ( ) ;
subToPusherMessage . setSendusermessage ( sendUserMessage ) ;
recipient . socket . write ( subToPusherMessage ) ;
}
public banUser ( roomId : string , recipientUuid : string ) : void {
const room = this . rooms . get ( roomId ) ;
if ( ! room ) {
console . error ( "In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" ) ;
return ;
}
const recipient = room . getUserByUuid ( recipientUuid ) ;
if ( recipient === undefined ) {
console . error ( "In banUser, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?" ) ;
return ;
}
// Let's leave the room now.
room . leave ( recipient ) ;
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setType ( 'banned' ) ;
const subToPusherMessage = new SubToPusherMessage ( ) ;
subToPusherMessage . setSendusermessage ( sendUserMessage ) ;
recipient . socket . write ( subToPusherMessage ) ;
// Let's close the connection when the user is banned.
recipient . socket . end ( ) ;
}
2020-10-15 17:25:16 +02:00
}
2020-10-16 19:13:26 +02:00
export const socketManager = new SocketManager ( ) ;