2020-10-15 17:25:16 +02:00
import { GameRoom } from "../Model/GameRoom" ;
import {
ItemEventMessage ,
2020-10-16 19:13:26 +02:00
ItemStateMessage ,
PlayGlobalMessage ,
2020-10-15 17:25:16 +02:00
PointMessage ,
RoomJoinedMessage ,
ServerToClientMessage ,
SilentMessage ,
SubMessage ,
UserMovedMessage ,
UserMovesMessage ,
2020-11-13 18:00:22 +01:00
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 ,
BatchToPusherMessage ,
SubToPusherMessage ,
2021-03-01 17:47:00 +01:00
UserJoinedZoneMessage ,
GroupUpdateZoneMessage ,
GroupLeftZoneMessage ,
2021-03-11 16:14:34 +01:00
WorldFullWarningMessage ,
2021-03-01 17:47:00 +01:00
UserLeftZoneMessage ,
BanUserMessage ,
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" ;
2021-02-16 09:58:08 +01:00
import {
GROUP_RADIUS ,
JITSI_ISS ,
MINIMUM_DISTANCE ,
SECRET_JITSI_KEY ,
TURN_STATIC_AUTH_SECRET
} from "../Enum/EnvironmentVariable" ;
2020-10-15 17:25:16 +02:00
import { Movable } from "../Model/Movable" ;
import { PositionInterface } from "../Model/PositionInterface" ;
2021-01-17 03:07:46 +01:00
import { adminApi , CharacterTexture } 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" ;
2021-01-17 03:07:46 +01:00
import { 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" ;
2021-02-16 09:58:08 +01:00
import crypto from "crypto" ;
2020-11-13 18:00:22 +01:00
const debug = Debug ( 'sockermanager' ) ;
2020-10-16 14:36:43 +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 ) ;
2021-03-11 16:14:34 +01:00
2020-11-13 18:00:22 +01:00
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-11-13 18:00:22 +01:00
public async handleJoinRoom ( socket : UserSocket , joinRoomMessage : JoinRoomMessage ) : Promise < { room : GameRoom ; user : User } > {
2021-03-26 14:12:22 +01:00
2020-11-13 18:00:22 +01:00
//join new previous room
const { room , user } = await this . joinRoom ( socket , joinRoomMessage ) ;
2021-03-26 14:12:22 +01:00
2020-11-13 18:00:22 +01:00
const roomJoinedMessage = new RoomJoinedMessage ( ) ;
2021-01-17 03:07:46 +01:00
roomJoinedMessage . setTagList ( joinRoomMessage . getTagList ( ) ) ;
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 ) ;
console . log ( 'SENDING MESSAGE roomJoinedMessage' ) ;
socket . write ( serverToClientMessage ) ;
return {
room ,
user
} ;
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 ( ) ) ;
2021-02-16 18:13:30 +01:00
// TODO: only compute credentials if data.signal.type === "offer"
if ( TURN_STATIC_AUTH_SECRET !== '' ) {
const { username , password } = this . getTURNCredentials ( '' + user . id , TURN_STATIC_AUTH_SECRET ) ;
webrtcSignalToClient . setWebrtcusername ( username ) ;
webrtcSignalToClient . setWebrtcpassword ( password ) ;
}
2020-10-15 17:25:16 +02:00
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 ( ) ) ;
2021-02-16 18:13:30 +01:00
// TODO: only compute credentials if data.signal.type === "offer"
if ( TURN_STATIC_AUTH_SECRET !== '' ) {
const { username , password } = this . getTURNCredentials ( '' + user . id , TURN_STATIC_AUTH_SECRET ) ;
webrtcSignalToClient . setWebrtcusername ( username ) ;
webrtcSignalToClient . setWebrtcpassword ( password ) ;
}
2020-10-15 17:25:16 +02:00
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 ) ;
2020-12-11 13:00:11 +01:00
gaugeManager . decNbRoomGauge ( ) ;
2020-11-13 18:00:22 +01:00
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-12 14:51:19 +01:00
gaugeManager . incNbRoomGauge ( ) ;
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
2021-03-11 16:14:34 +01:00
const room = await socketManager . getOrCreateRoom ( roomId ) ;
2020-11-13 18:00:22 +01:00
2020-10-15 17:25:16 +02:00
//join world
2021-03-11 16:14:34 +01:00
const user = room . 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' ) ;
2021-03-11 16:14:34 +01:00
return { room , 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 ) ;
2021-02-16 09:58:08 +01:00
if ( TURN_STATIC_AUTH_SECRET !== '' ) {
const { username , password } = this . getTURNCredentials ( '' + otherUser . id , TURN_STATIC_AUTH_SECRET ) ;
webrtcStartMessage1 . setWebrtcusername ( username ) ;
webrtcStartMessage1 . setWebrtcpassword ( password ) ;
}
2020-10-15 17:25:16 +02:00
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 ) ;
2021-02-16 09:58:08 +01:00
if ( TURN_STATIC_AUTH_SECRET !== '' ) {
const { username , password } = this . getTURNCredentials ( '' + user . id , TURN_STATIC_AUTH_SECRET ) ;
webrtcStartMessage2 . setWebrtcusername ( username ) ;
webrtcStartMessage2 . setWebrtcpassword ( password ) ;
}
2020-10-15 17:25:16 +02:00
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
}
}
2021-02-16 09:58:08 +01:00
/ * *
* Computes a unique user / password for the TURN server , using a shared secret between the WorkAdventure API server
* and the Coturn server .
* The Coturn server should be initialized with parameters : ` --use-auth-secret --static-auth-secret=MySecretKey `
* /
private getTURNCredentials ( name : string , secret : string ) : { username : string , password : string } {
const unixTimeStamp = Math . floor ( Date . now ( ) / 1000 ) + 4 * 3600 ; // this credential would be valid for the next 4 hours
const username = [ unixTimeStamp , name ] . join ( ':' ) ;
const hmac = crypto . createHmac ( 'sha1' , secret ) ;
hmac . setEncoding ( 'base64' ) ;
hmac . write ( username ) ;
hmac . end ( ) ;
const password = hmac . read ( ) ;
return {
username : username ,
password : password
} ;
}
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
2021-01-17 03:07:46 +01:00
public handlerSendUserMessage ( user : User , sendUserMessageToSend : SendUserMessage ) {
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setMessage ( sendUserMessageToSend . getMessage ( ) ) ;
sendUserMessage . setType ( sendUserMessageToSend . getType ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setSendusermessage ( sendUserMessage ) ;
user . socket . write ( serverToClientMessage ) ;
}
public handlerBanUserMessage ( room : GameRoom , user : User , banUserMessageToSend : BanUserMessage ) {
const banUserMessage = new BanUserMessage ( ) ;
banUserMessage . setMessage ( banUserMessageToSend . getMessage ( ) ) ;
banUserMessage . setType ( banUserMessageToSend . getType ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setSendusermessage ( banUserMessage ) ;
user . socket . write ( serverToClientMessage ) ;
setTimeout ( ( ) = > {
// Let's leave the room now.
room . leave ( user ) ;
// Let's close the connection when the user is banned.
user . socket . end ( ) ;
} , 10000 ) ;
}
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 ) ;
room . adminJoin ( admin ) ;
return room ;
}
public leaveAdminRoom ( room : GameRoom , admin : Admin ) {
room . adminLeave ( admin ) ;
if ( room . isEmpty ( ) ) {
this . rooms . delete ( room . roomId ) ;
2020-12-11 13:00:11 +01:00
gaugeManager . decNbRoomGauge ( ) ;
2020-12-10 17:46:15 +01:00
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 ) {
2021-03-11 01:25:36 +01:00
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?" ) ;
2020-12-11 12:23:50 +01:00
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 ) ;
2021-03-01 17:47:00 +01:00
sendUserMessage . setType ( 'ban' ) ; //todo: is the type correct?
2020-12-11 12:23:50 +01:00
2021-03-11 01:25:36 +01:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setSendusermessage ( sendUserMessage ) ;
2020-12-11 12:23:50 +01:00
2021-03-11 01:25:36 +01:00
recipient . socket . write ( serverToClientMessage ) ;
2020-12-11 12:23:50 +01:00
}
2021-01-17 03:07:46 +01:00
public banUser ( roomId : string , recipientUuid : string , message : string ) : void {
2020-12-11 12:23:50 +01:00
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 ) ;
2021-03-11 01:25:36 +01:00
const banUserMessage = new BanUserMessage ( ) ;
banUserMessage . setMessage ( message ) ;
banUserMessage . setType ( 'banned' ) ;
2020-12-11 12:23:50 +01:00
2021-03-11 01:25:36 +01:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setBanusermessage ( banUserMessage ) ;
2020-12-11 12:23:50 +01:00
// Let's close the connection when the user is banned.
2021-03-11 01:25:36 +01:00
recipient . socket . write ( serverToClientMessage ) ;
2020-12-11 12:23:50 +01:00
recipient . socket . end ( ) ;
}
2021-03-01 17:47:00 +01:00
sendAdminRoomMessage ( roomId : string , message : string ) {
const room = this . rooms . get ( roomId ) ;
if ( ! room ) {
//todo: this should cause the http call to return a 500
console . error ( "In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" ) ;
return ;
}
room . getUsers ( ) . forEach ( ( recipient ) = > {
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setMessage ( message ) ;
sendUserMessage . setType ( 'message' ) ;
const clientMessage = new ServerToClientMessage ( ) ;
clientMessage . setSendusermessage ( sendUserMessage ) ;
recipient . socket . write ( clientMessage ) ;
} ) ;
}
2021-03-11 16:14:34 +01:00
dispatchWorlFullWarning ( roomId : string , ) : void {
const room = this . rooms . get ( roomId ) ;
if ( ! room ) {
//todo: this should cause the http call to return a 500
console . error ( "In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" ) ;
return ;
}
room . getUsers ( ) . forEach ( ( recipient ) = > {
const worldFullMessage = new WorldFullWarningMessage ( ) ;
const clientMessage = new ServerToClientMessage ( ) ;
clientMessage . setWorldfullwarningmessage ( worldFullMessage ) ;
recipient . socket . write ( clientMessage ) ;
} ) ;
}
2020-10-15 17:25:16 +02:00
}
2020-10-16 19:13:26 +02:00
export const socketManager = new SocketManager ( ) ;