2021-06-24 10:09:10 +02:00
import { GameRoom } from "../Model/GameRoom" ;
2020-10-15 17:25:16 +02:00
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 ,
2021-03-31 11:21:06 +02:00
EmoteEventMessage ,
2021-06-24 10:09:10 +02:00
BanUserMessage ,
RefreshRoomMessage ,
EmotePromptMessage ,
2021-07-16 11:22:36 +02:00
VariableMessage ,
BatchToPusherRoomMessage ,
SubToPusherRoomMessage ,
2020-10-15 17:25:16 +02:00
} from "../Messages/generated/messages_pb" ;
2021-06-24 10:09:10 +02:00
import { User , UserSocket } from "../Model/User" ;
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 ,
2021-06-24 10:09:10 +02:00
TURN_STATIC_AUTH_SECRET ,
2021-02-16 09:58:08 +01:00
} from "../Enum/EnvironmentVariable" ;
2021-06-24 10:09:10 +02:00
import { Movable } from "../Model/Movable" ;
import { PositionInterface } from "../Model/PositionInterface" ;
2020-10-16 19:13:26 +02:00
import Jwt from "jsonwebtoken" ;
2021-06-24 10:09:10 +02:00
import { JITSI_URL } from "../Enum/EnvironmentVariable" ;
import { clientEventsEmitter } from "./ClientEventsEmitter" ;
import { gaugeManager } from "./GaugeManager" ;
2021-07-16 11:22:36 +02:00
import { RoomSocket , ZoneSocket } from "../RoomManager" ;
2021-06-24 10:09:10 +02:00
import { Zone } from "_Model/Zone" ;
2020-11-13 18:00:22 +01:00
import Debug from "debug" ;
2021-06-24 10:09:10 +02:00
import { Admin } from "_Model/Admin" ;
2021-02-16 09:58:08 +01:00
import crypto from "crypto" ;
2021-11-08 17:44:44 +01:00
import log from "./Logger" ;
2021-02-16 09:58:08 +01:00
2021-06-24 10:09:10 +02: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 ) ;
socket . write ( batchMessage ) ;
}
2020-10-20 16:39:23 +02:00
export class SocketManager {
2021-07-19 10:16:43 +02:00
//private rooms = new Map<string, GameRoom>();
2021-07-07 17:17:28 +02:00
// List of rooms in process of loading.
private roomsPromises = new Map < string , PromiseLike < GameRoom > > ( ) ;
2020-11-13 18:00:22 +01:00
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
2021-06-24 10:09:10 +02:00
public async handleJoinRoom (
socket : UserSocket ,
joinRoomMessage : JoinRoomMessage
) : Promise < { room : GameRoom ; user : User } > {
2020-11-13 18:00:22 +01:00
//join new previous room
2021-06-24 10:09:10 +02:00
const { room , user } = await this . joinRoom ( socket , joinRoomMessage ) ;
2021-03-26 15:13:08 +01:00
if ( ! socket . writable ) {
2021-11-08 17:44:44 +01:00
log . warn ( "Socket was aborted" ) ;
2021-03-26 15:13:08 +01:00
return {
room ,
2021-06-24 10:09:10 +02:00
user ,
2021-03-26 15:13:08 +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
}
2021-07-19 10:16:43 +02:00
const variables = await room . getVariablesForTags ( user . tags ) ;
for ( const [ name , value ] of variables . entries ( ) ) {
2021-07-07 17:17:28 +02:00
const variableMessage = new VariableMessage ( ) ;
variableMessage . setName ( name ) ;
variableMessage . setValue ( value ) ;
roomJoinedMessage . addVariable ( variableMessage ) ;
}
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 ) ;
socket . write ( serverToClientMessage ) ;
return {
room ,
2021-06-24 10:09:10 +02:00
user ,
2020-11-13 18:00:22 +01:00
} ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
handleUserMovesMessage ( room : GameRoom , user : User , userMovesMessage : UserMovesMessage ) {
2021-07-22 10:33:07 +02:00
const userMoves = userMovesMessage . toObject ( ) ;
const position = userMovesMessage . getPosition ( ) ;
2020-10-15 17:25:16 +02:00
2021-07-22 10:33:07 +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 ;
}
2020-10-15 17:25:16 +02:00
2021-07-22 10:33:07 +02:00
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" ) ;
2020-10-15 17:25:16 +02:00
}
2021-07-22 10:33:07 +02:00
// update position in the world
room . updatePosition ( user , ProtobufUtils . toPointInterface ( position ) ) ;
//room.setViewport(client, client.viewport);
2020-10-15 17:25:16 +02:00
}
// 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 ( )
} ;
2021-11-08 17:44:44 +01:00
//log.info(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
2020-10-15 17:25:16 +02:00
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 ) {
2021-07-22 10:33:07 +02:00
room . setSilent ( user , silentMessage . getSilent ( ) ) ;
2020-10-15 17:25:16 +02:00
}
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 ) ;
2021-07-22 10:33:07 +02:00
const subMessage = new SubMessage ( ) ;
subMessage . setItemeventmessage ( itemEventMessage ) ;
2020-10-15 17:25:16 +02:00
2021-07-22 10:33:07 +02:00
// Let's send the event without using the SocketIO room.
// 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
}
2021-07-22 10:33:07 +02:00
room . setItemState ( itemEvent . itemId , itemEvent . state ) ;
2020-10-15 17:25:16 +02:00
}
2021-07-22 10:33:07 +02:00
handleVariableEvent ( room : GameRoom , user : User , variableMessage : VariableMessage ) : Promise < void > {
return room . setVariable ( variableMessage . getName ( ) , variableMessage . getValue ( ) , user ) ;
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 ) {
2021-11-08 17:44:44 +01:00
log . warn (
2021-06-24 10:09:10 +02:00
"While exchanging a WebRTC signal: client with id " ,
data . getReceiverid ( ) ,
" does not exist. This might be a race condition."
) ;
2020-10-15 17:25:16 +02:00
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"
2021-06-24 10:09:10 +02:00
if ( TURN_STATIC_AUTH_SECRET !== "" ) {
const { username , password } = this . getTURNCredentials ( "" + user . id , TURN_STATIC_AUTH_SECRET ) ;
2021-02-16 18:13:30 +01:00
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) {
2021-06-24 10:09:10 +02:00
remoteUser . socket . write ( serverToClientMessage ) ;
2020-11-13 18:00:22 +01:00
//}
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 ) {
2021-11-08 17:44:44 +01:00
log . warn (
2021-06-24 10:09:10 +02:00
"While exchanging a WEBRTC_SCREEN_SHARING signal: client with id " ,
data . getReceiverid ( ) ,
" does not exist. This might be a race condition."
) ;
2020-10-15 17:25:16 +02:00
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"
2021-06-24 10:09:10 +02:00
if ( TURN_STATIC_AUTH_SECRET !== "" ) {
const { username , password } = this . getTURNCredentials ( "" + user . id , TURN_STATIC_AUTH_SECRET ) ;
2021-02-16 18:13:30 +01:00
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) {
2021-06-24 10:09:10 +02:00
remoteUser . socket . write ( serverToClientMessage ) ;
2020-11-13 18:00:22 +01:00
//}
2020-10-15 17:25:16 +02:00
}
2021-06-24 10:09:10 +02: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 ( ) ) {
2021-07-19 10:16:43 +02:00
this . roomsPromises . delete ( room . roomUrl ) ;
2020-12-11 13:00:11 +01:00
gaugeManager . decNbRoomGauge ( ) ;
2021-07-13 19:09:07 +02:00
debug ( 'Room is empty. Deleting room "%s"' , room . roomUrl ) ;
2020-10-15 17:25:16 +02:00
}
2020-11-13 18:00:22 +01:00
} finally {
2021-07-13 19:09:07 +02:00
clientEventsEmitter . emitClientLeave ( user . uuid , room . roomUrl ) ;
2021-11-08 17:44:44 +01:00
log . info ( "A user left" ) ;
2020-10-15 17:25:16 +02:00
}
}
async getOrCreateRoom ( roomId : string ) : Promise < GameRoom > {
2021-07-07 17:17:28 +02:00
//check and create new room
2021-07-19 10:16:43 +02:00
let roomPromise = this . roomsPromises . get ( roomId ) ;
if ( roomPromise === undefined ) {
2021-07-22 10:41:45 +02:00
roomPromise = GameRoom . create (
2020-10-15 17:25:16 +02:00
roomId ,
( user : User , group : Group ) = > this . joinWebRtcRoom ( user , group ) ,
( user : User , group : Group ) = > this . disConnectedUser ( user , group ) ,
MINIMUM_DISTANCE ,
GROUP_RADIUS ,
2021-06-24 10:09:10 +02: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 ) ,
( emoteEventMessage : EmoteEventMessage , listener : ZoneSocket ) = >
this . onEmote ( emoteEventMessage , listener )
2021-07-22 10:41:45 +02:00
)
. then ( ( gameRoom ) = > {
gaugeManager . incNbRoomGauge ( ) ;
return gameRoom ;
} )
. catch ( ( e ) = > {
this . roomsPromises . delete ( roomId ) ;
throw e ;
} ) ;
2021-07-19 10:16:43 +02:00
this . roomsPromises . set ( roomId , roomPromise ) ;
2020-10-15 17:25:16 +02:00
}
2021-07-19 10:16:43 +02:00
return roomPromise ;
2020-10-15 17:25:16 +02:00
}
2021-06-24 10:09:10 +02:00
private async joinRoom (
socket : UserSocket ,
joinRoomMessage : JoinRoomMessage
) : Promise < { room : GameRoom ; user : User } > {
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 ) ;
2021-11-08 17:44:44 +01:00
log . info ( new Date ( ) . toISOString ( ) + " user '" + user . uuid + "' joined room '" + roomId + "'" ) ;
2021-06-24 10:09:10 +02:00
return { room , user } ;
2020-10-15 17:25:16 +02:00
}
2021-06-24 10:09:10 +02: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 ) ) {
2021-06-24 10:09:10 +02:00
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 ) ;
2021-07-07 11:24:51 +02:00
userJoinedZoneMessage . setUseruuid ( thing . uuid ) ;
2020-11-13 18:00:22 +01:00
userJoinedZoneMessage . setName ( thing . name ) ;
userJoinedZoneMessage . setCharacterlayersList ( ProtobufUtils . toCharacterLayerMessages ( thing . characterLayers ) ) ;
userJoinedZoneMessage . setPosition ( ProtobufUtils . toPositionMessage ( thing . getPosition ( ) ) ) ;
userJoinedZoneMessage . setFromzone ( this . toProtoZone ( fromZone ) ) ;
2021-06-08 16:30:58 +02:00
if ( thing . visitCardUrl ) {
userJoinedZoneMessage . setVisitcardurl ( thing . visitCardUrl ) ;
}
2021-04-02 21:21:11 +02:00
userJoinedZoneMessage . setCompanion ( thing . companion ) ;
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 {
2021-11-08 17:44:44 +01:00
log . error ( "Unexpected type for Movable." ) ;
2020-10-15 17:25:16 +02:00
}
}
2021-06-24 10:09:10 +02: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);
2021-11-08 17:44:44 +01:00
//log.info("Sending USER_MOVED event");
2020-10-15 17:25:16 +02:00
} 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 {
2021-11-08 17:44:44 +01:00
log . error ( "Unexpected type for Movable." ) ;
2020-10-15 17:25:16 +02:00
}
}
2021-06-24 10:09:10 +02: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 {
2021-11-08 17:44:44 +01:00
log . error ( "Unexpected type for Movable." ) ;
2020-10-15 17:25:16 +02:00
}
}
2021-03-31 11:21:06 +02:00
private onEmote ( emoteEventMessage : EmoteEventMessage , client : ZoneSocket ) {
const subMessage = new SubToPusherMessage ( ) ;
subMessage . setEmoteeventmessage ( emoteEventMessage ) ;
emitZoneMessage ( subMessage , client ) ;
}
2021-06-24 10:09:10 +02: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
}
2021-06-24 10:09:10 +02:00
private emitDeleteGroupEvent ( client : ZoneSocket , groupId : number , newZone : Zone | null ) : void {
2020-11-13 18:00:22 +01:00
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
}
2021-06-24 10:09:10 +02:00
private emitUserLeftEvent ( client : ZoneSocket , userId : number , newZone : Zone | null ) : void {
2020-11-13 18:00:22 +01:00
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
2021-06-24 10:09:10 +02:00
private toProtoZone ( zone : Zone | null ) : ProtoZone | undefined {
2020-11-13 18:00:22 +01:00
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 ) {
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 ) ;
webrtcStartMessage1 . setInitiator ( true ) ;
2021-06-24 10:09:10 +02:00
if ( TURN_STATIC_AUTH_SECRET !== "" ) {
const { username , password } = this . getTURNCredentials ( "" + otherUser . id , TURN_STATIC_AUTH_SECRET ) ;
2021-02-16 09:58:08 +01:00
webrtcStartMessage1 . setWebrtcusername ( username ) ;
webrtcStartMessage1 . setWebrtcpassword ( password ) ;
}
2020-10-15 17:25:16 +02:00
const serverToClientMessage1 = new ServerToClientMessage ( ) ;
serverToClientMessage1 . setWebrtcstartmessage ( webrtcStartMessage1 ) ;
2021-06-24 10:09:10 +02:00
user . socket . write ( serverToClientMessage1 ) ;
2020-10-15 17:25:16 +02:00
const webrtcStartMessage2 = new WebRtcStartMessage ( ) ;
webrtcStartMessage2 . setUserid ( user . id ) ;
webrtcStartMessage2 . setInitiator ( false ) ;
2021-06-24 10:09:10 +02:00
if ( TURN_STATIC_AUTH_SECRET !== "" ) {
const { username , password } = this . getTURNCredentials ( "" + user . id , TURN_STATIC_AUTH_SECRET ) ;
2021-02-16 09:58:08 +01:00
webrtcStartMessage2 . setWebrtcusername ( username ) ;
webrtcStartMessage2 . setWebrtcpassword ( password ) ;
}
2020-10-15 17:25:16 +02:00
const serverToClientMessage2 = new ServerToClientMessage ( ) ;
serverToClientMessage2 . setWebrtcstartmessage ( webrtcStartMessage2 ) ;
2021-06-24 10:09:10 +02:00
otherUser . socket . write ( serverToClientMessage2 ) ;
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 `
* /
2021-06-24 10:09:10 +02:00
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" ) ;
2021-02-16 09:58:08 +01:00
hmac . write ( username ) ;
hmac . end ( ) ;
const password = hmac . read ( ) ;
return {
username : username ,
2021-06-24 10:09:10 +02:00
password : password ,
2021-02-16 09:58:08 +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) {
2021-06-24 10:09:10 +02:00
otherUser . socket . write ( serverToClientMessage1 ) ;
2020-11-13 18:00:22 +01:00
//}
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) {
2021-06-24 10:09:10 +02:00
user . socket . write ( serverToClientMessage2 ) ;
2020-11-13 18:00:22 +01:00
//}
2020-10-15 17:25:16 +02:00
}
}
2020-11-13 18:00:22 +01:00
emitPlayGlobalMessage ( room : GameRoom , playGlobalMessage : PlayGlobalMessage ) {
2021-07-22 10:33:07 +02:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setPlayglobalmessage ( playGlobalMessage ) ;
2020-10-15 17:25:16 +02:00
2021-07-22 10:33:07 +02:00
for ( const [ id , user ] of room . getUsers ( ) . entries ( ) ) {
user . socket . write ( serverToClientMessage ) ;
2020-10-15 17:25:16 +02:00
}
}
2021-07-19 10:16:43 +02:00
public getWorlds ( ) : Map < string , PromiseLike < GameRoom > > {
return this . roomsPromises ;
2020-10-15 17:25:16 +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.
2021-06-24 10:09:10 +02:00
if ( SECRET_JITSI_KEY === "" ) {
throw new Error ( "You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi." ) ;
2020-10-16 19:13:26 +02:00
}
// 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
2021-06-24 10:09:10 +02:00
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" ,
} ,
}
) ;
2020-10-16 19:13:26 +02:00
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-06-24 10:09:10 +02:00
public handlerSendUserMessage ( user : User , sendUserMessageToSend : SendUserMessage ) {
2021-01-17 03:07:46 +01:00
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setMessage ( sendUserMessageToSend . getMessage ( ) ) ;
sendUserMessage . setType ( sendUserMessageToSend . getType ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setSendusermessage ( sendUserMessage ) ;
user . socket . write ( serverToClientMessage ) ;
}
2021-06-24 10:09:10 +02:00
public handlerBanUserMessage ( room : GameRoom , user : User , banUserMessageToSend : BanUserMessage ) {
2021-01-17 03:07:46 +01:00
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 ) ;
}
2021-07-19 10:16:43 +02:00
public async addZoneListener ( call : ZoneSocket , roomId : string , x : number , y : number ) : Promise < void > {
const room = await this . roomsPromises . get ( roomId ) ;
2020-11-13 18:00:22 +01:00
if ( ! room ) {
2021-07-19 10:16:43 +02:00
throw new Error ( "In addZoneListener, could not find room with id '" + roomId + "'" ) ;
2020-11-13 18:00:22 +01:00
}
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 ) ;
2021-07-07 11:24:51 +02:00
userJoinedMessage . setUseruuid ( thing . uuid ) ;
2020-11-13 18:00:22 +01:00
userJoinedMessage . setName ( thing . name ) ;
userJoinedMessage . setCharacterlayersList ( ProtobufUtils . toCharacterLayerMessages ( thing . characterLayers ) ) ;
userJoinedMessage . setPosition ( ProtobufUtils . toPositionMessage ( thing . getPosition ( ) ) ) ;
2021-06-08 16:30:58 +02:00
if ( thing . visitCardUrl ) {
userJoinedMessage . setVisitcardurl ( thing . visitCardUrl ) ;
}
2021-04-02 21:21:11 +02:00
userJoinedMessage . setCompanion ( thing . companion ) ;
2020-11-13 18:00:22 +01:00
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 {
2021-11-08 17:44:44 +01:00
log . error ( "Unexpected type for Movable returned by setViewport" ) ;
2020-11-13 18:00:22 +01:00
}
}
call . write ( batchMessage ) ;
}
2021-07-19 10:16:43 +02:00
async removeZoneListener ( call : ZoneSocket , roomId : string , x : number , y : number ) : Promise < void > {
const room = await this . roomsPromises . get ( roomId ) ;
2020-11-13 18:00:22 +01:00
if ( ! room ) {
2021-07-19 10:16:43 +02:00
throw new Error ( "In removeZoneListener, could not find room with id '" + roomId + "'" ) ;
2020-11-13 18:00:22 +01:00
}
room . removeZoneListener ( call , x , y ) ;
}
2020-12-10 17:46:15 +01:00
2021-07-07 17:17:28 +02:00
async addRoomListener ( call : RoomSocket , roomId : string ) {
const room = await this . getOrCreateRoom ( roomId ) ;
if ( ! room ) {
2021-07-19 10:16:43 +02:00
throw new Error ( "In addRoomListener, could not find room with id '" + roomId + "'" ) ;
2021-07-07 17:17:28 +02:00
}
room . addRoomListener ( call ) ;
const batchMessage = new BatchToPusherRoomMessage ( ) ;
call . write ( batchMessage ) ;
}
2021-07-19 10:16:43 +02:00
async removeRoomListener ( call : RoomSocket , roomId : string ) {
const room = await this . roomsPromises . get ( roomId ) ;
2021-07-07 17:17:28 +02:00
if ( ! room ) {
2021-07-19 10:16:43 +02:00
throw new Error ( "In removeRoomListener, could not find room with id '" + roomId + "'" ) ;
2021-07-07 17:17:28 +02:00
}
room . removeRoomListener ( call ) ;
}
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 ;
}
2021-06-24 10:09:10 +02:00
public leaveAdminRoom ( room : GameRoom , admin : Admin ) {
2020-12-10 17:46:15 +01:00
room . adminLeave ( admin ) ;
if ( room . isEmpty ( ) ) {
2021-07-19 10:16:43 +02:00
this . roomsPromises . delete ( room . roomUrl ) ;
2020-12-11 13:00:11 +01:00
gaugeManager . decNbRoomGauge ( ) ;
2021-07-13 19:09:07 +02:00
debug ( 'Room is empty. Deleting room "%s"' , room . roomUrl ) ;
2020-12-10 17:46:15 +01:00
}
}
2021-07-19 10:16:43 +02:00
public async sendAdminMessage ( roomId : string , recipientUuid : string , message : string ) : Promise < void > {
const room = await this . roomsPromises . get ( roomId ) ;
2020-12-11 12:23:50 +01:00
if ( ! room ) {
2021-11-08 17:44:44 +01:00
log . error (
2021-06-24 10:09:10 +02:00
"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 ;
}
2021-07-27 14:42:32 +02:00
const recipients = room . getUsersByUuid ( recipientUuid ) ;
if ( recipients . length === 0 ) {
2021-11-08 17:44:44 +01:00
log . error (
2021-06-24 10:09:10 +02:00
"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?"
) ;
2020-12-11 12:23:50 +01:00
return ;
}
2021-07-27 14:42:32 +02:00
for ( const recipient of recipients ) {
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setMessage ( message ) ;
sendUserMessage . setType ( "ban" ) ; //todo: is the type correct?
2020-12-11 12:23:50 +01:00
2021-07-27 14:42:32 +02:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setSendusermessage ( sendUserMessage ) ;
2020-12-11 12:23:50 +01:00
2021-07-27 14:42:32 +02:00
recipient . socket . write ( serverToClientMessage ) ;
}
2020-12-11 12:23:50 +01:00
}
2021-07-19 10:16:43 +02:00
public async banUser ( roomId : string , recipientUuid : string , message : string ) : Promise < void > {
const room = await this . roomsPromises . get ( roomId ) ;
2020-12-11 12:23:50 +01:00
if ( ! room ) {
2021-11-08 17:44:44 +01:00
log . error (
2021-06-24 10:09:10 +02:00
"In banUser, 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 ;
}
2021-07-27 14:42:32 +02:00
const recipients = room . getUsersByUuid ( recipientUuid ) ;
if ( recipients . length === 0 ) {
2021-11-08 17:44:44 +01:00
log . error (
2021-06-24 10:09:10 +02:00
"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?"
) ;
2020-12-11 12:23:50 +01:00
return ;
}
2021-07-27 14:42:32 +02:00
for ( const recipient of recipients ) {
// Let's leave the room now.
room . leave ( recipient ) ;
2020-12-11 12:23:50 +01:00
2021-07-27 14:42:32 +02:00
const banUserMessage = new BanUserMessage ( ) ;
banUserMessage . setMessage ( message ) ;
banUserMessage . setType ( "banned" ) ;
2020-12-11 12:23:50 +01:00
2021-07-27 14:42:32 +02:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setBanusermessage ( banUserMessage ) ;
2020-12-11 12:23:50 +01:00
2021-07-27 14:42:32 +02:00
// Let's close the connection when the user is banned.
recipient . socket . write ( serverToClientMessage ) ;
recipient . socket . end ( ) ;
}
2020-12-11 12:23:50 +01:00
}
2021-03-01 17:47:00 +01:00
2021-07-22 16:26:01 +02:00
async sendAdminRoomMessage ( roomId : string , message : string , type : string ) {
2021-07-19 10:16:43 +02:00
const room = await this . roomsPromises . get ( roomId ) ;
2021-03-01 17:47:00 +01:00
if ( ! room ) {
//todo: this should cause the http call to return a 500
2021-11-08 17:44:44 +01:00
log . error (
2021-06-24 10:09:10 +02:00
"In sendAdminRoomMessage, could not find room with id '" +
roomId +
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
) ;
2021-03-01 17:47:00 +01:00
return ;
}
room . getUsers ( ) . forEach ( ( recipient ) = > {
const sendUserMessage = new SendUserMessage ( ) ;
sendUserMessage . setMessage ( message ) ;
2021-07-22 16:14:27 +02:00
sendUserMessage . setType ( type ) ;
2021-03-01 17:47:00 +01:00
const clientMessage = new ServerToClientMessage ( ) ;
clientMessage . setSendusermessage ( sendUserMessage ) ;
recipient . socket . write ( clientMessage ) ;
} ) ;
}
2021-03-11 16:14:34 +01:00
2021-07-19 10:16:43 +02:00
async dispatchWorldFullWarning ( roomId : string ) : Promise < void > {
const room = await this . roomsPromises . get ( roomId ) ;
2021-03-11 16:14:34 +01:00
if ( ! room ) {
//todo: this should cause the http call to return a 500
2021-11-08 17:44:44 +01:00
log . error (
2021-07-29 18:02:36 +02:00
"In dispatchWorldFullWarning, could not find room with id '" +
2021-06-24 10:09:10 +02:00
roomId +
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
) ;
2021-03-11 16:14:34 +01:00
return ;
}
2021-06-24 10:09:10 +02:00
2021-03-11 16:14:34 +01:00
room . getUsers ( ) . forEach ( ( recipient ) = > {
const worldFullMessage = new WorldFullWarningMessage ( ) ;
const clientMessage = new ServerToClientMessage ( ) ;
clientMessage . setWorldfullwarningmessage ( worldFullMessage ) ;
recipient . socket . write ( clientMessage ) ;
} ) ;
}
2021-04-01 16:43:12 +02:00
2021-07-19 10:16:43 +02:00
async dispatchRoomRefresh ( roomId : string ) : Promise < void > {
const room = await this . roomsPromises . get ( roomId ) ;
2021-04-01 16:43:12 +02:00
if ( ! room ) {
return ;
}
2021-06-24 10:09:10 +02:00
2021-04-01 16:43:12 +02:00
const versionNumber = room . incrementVersion ( ) ;
room . getUsers ( ) . forEach ( ( recipient ) = > {
const worldFullMessage = new RefreshRoomMessage ( ) ;
2021-06-24 10:09:10 +02:00
worldFullMessage . setRoomid ( roomId ) ;
worldFullMessage . setVersionnumber ( versionNumber ) ;
2021-04-01 16:43:12 +02:00
const clientMessage = new ServerToClientMessage ( ) ;
clientMessage . setRefreshroommessage ( worldFullMessage ) ;
recipient . socket . write ( clientMessage ) ;
} ) ;
}
2021-03-31 11:21:06 +02:00
handleEmoteEventMessage ( room : GameRoom , user : User , emotePromptMessage : EmotePromptMessage ) {
const emoteEventMessage = new EmoteEventMessage ( ) ;
emoteEventMessage . setEmote ( emotePromptMessage . getEmote ( ) ) ;
emoteEventMessage . setActoruserid ( user . id ) ;
room . emitEmoteEvent ( user , emoteEventMessage ) ;
}
2020-10-15 17:25:16 +02:00
}
2020-10-16 19:13:26 +02:00
export const socketManager = new SocketManager ( ) ;