2020-04-04 17:22:02 +02:00
import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface" ; //TODO fix import by "_Model/.."
2020-10-09 14:53:18 +02:00
import { MINIMUM_DISTANCE , GROUP_RADIUS } from "../Enum/EnvironmentVariable" ; //TODO fix import by "_Enum/..."
2020-10-06 15:37:00 +02:00
import { GameRoom } from "../Model/GameRoom" ;
2020-09-15 10:06:11 +02:00
import { Group } from "../Model/Group" ;
2020-09-16 16:06:43 +02:00
import { User } from "../Model/User" ;
2020-06-09 23:07:19 +02:00
import { isSetPlayerDetailsMessage , } from "../Model/Websocket/SetPlayerDetailsMessage" ;
2020-06-09 11:49:23 +02:00
import { Gauge } from "prom-client" ;
2020-09-21 11:24:03 +02:00
import { PointInterface } from "../Model/Websocket/PointInterface" ;
2020-09-16 16:06:43 +02:00
import { Movable } from "../Model/Movable" ;
2020-09-18 15:51:15 +02:00
import {
PositionMessage ,
SetPlayerDetailsMessage ,
SubMessage ,
UserMovedMessage ,
2020-09-24 17:24:37 +02:00
BatchMessage ,
GroupUpdateMessage ,
PointMessage ,
GroupDeleteMessage ,
UserJoinedMessage ,
UserLeftMessage ,
2020-09-28 18:52:54 +02:00
ItemEventMessage ,
ViewportMessage ,
ClientToServerMessage ,
ErrorMessage ,
RoomJoinedMessage ,
ItemStateMessage ,
2020-09-29 16:01:22 +02:00
ServerToClientMessage ,
SilentMessage ,
WebRtcSignalToClientMessage ,
WebRtcSignalToServerMessage ,
2020-10-06 18:09:23 +02:00
WebRtcStartMessage ,
WebRtcDisconnectMessage ,
2020-10-06 15:37:00 +02:00
PlayGlobalMessage ,
2020-09-24 11:16:08 +02:00
} from "../Messages/generated/messages_pb" ;
import { UserMovesMessage } from "../Messages/generated/messages_pb" ;
2020-09-18 13:57:38 +02:00
import Direction = PositionMessage . Direction ;
2020-09-18 15:51:15 +02:00
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils" ;
2020-10-06 18:09:23 +02:00
import { TemplatedApp } from "uWebSockets.js"
2020-09-30 10:12:40 +02:00
import { parse } from "query-string" ;
2020-09-30 12:50:34 +02:00
import { cpuTracker } from "../Services/CpuTracker" ;
2020-10-06 18:09:23 +02:00
import { ViewportInterface } from "../Model/Websocket/ViewportMessage" ;
2020-10-09 14:53:18 +02:00
import { jwtTokenManager } from "../Services/JWTTokenManager" ;
import { adminApi } from "../Services/AdminApi" ;
2020-10-12 16:23:07 +02:00
import { RoomIdentifier } from "../Model/RoomIdentifier" ;
2020-04-29 01:40:32 +02:00
2020-09-28 18:52:54 +02:00
function emitInBatch ( socket : ExSocketInterface , payload : SubMessage ) : void {
2020-09-18 15:51:15 +02:00
socket . batchedMessages . addPayload ( payload ) ;
2020-09-15 10:06:11 +02:00
if ( socket . batchTimeout === null ) {
socket . batchTimeout = setTimeout ( ( ) = > {
2020-09-29 09:45:47 +02:00
if ( socket . disconnecting ) {
return ;
}
2020-09-28 18:52:54 +02:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setBatchmessage ( socket . batchedMessages ) ;
socket . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
2020-09-18 15:51:15 +02:00
socket . batchedMessages = new BatchMessage ( ) ;
2020-09-15 10:06:11 +02:00
socket . batchTimeout = null ;
} , 100 ) ;
}
2020-04-29 01:40:32 +02:00
}
2020-04-04 04:08:12 +02:00
2020-05-03 16:28:18 +02:00
export class IoSocketController {
2020-10-06 15:37:00 +02:00
private Worlds : Map < string , GameRoom > = new Map < string , GameRoom > ( ) ;
2020-09-18 13:57:38 +02:00
private sockets : Map < number , ExSocketInterface > = new Map < number , ExSocketInterface > ( ) ;
2020-06-09 11:49:23 +02:00
private nbClientsGauge : Gauge < string > ;
private nbClientsPerRoomGauge : Gauge < string > ;
2020-09-18 13:57:38 +02:00
private nextUserId : number = 1 ;
2020-05-03 16:28:18 +02:00
2020-09-28 18:52:54 +02:00
constructor ( private readonly app : TemplatedApp ) {
2020-05-03 16:28:18 +02:00
2020-06-09 11:49:23 +02:00
this . nbClientsGauge = new Gauge ( {
name : 'workadventure_nb_sockets' ,
help : 'Number of connected sockets' ,
2020-06-29 23:01:52 +02:00
labelNames : [ ]
2020-06-09 11:49:23 +02:00
} ) ;
this . nbClientsPerRoomGauge = new Gauge ( {
name : 'workadventure_nb_clients_per_room' ,
help : 'Number of clients per room' ,
2020-06-29 23:01:52 +02:00
labelNames : [ 'room' ]
2020-06-09 11:49:23 +02:00
} ) ;
2020-04-04 14:05:18 +02:00
2020-04-04 04:08:12 +02:00
this . ioConnection ( ) ;
2020-05-08 00:35:36 +02:00
}
2020-10-13 16:46:46 +02:00
2020-09-16 11:41:03 +02:00
2020-09-28 18:52:54 +02:00
ioConnection() {
2020-10-12 16:23:07 +02:00
this . app . ws ( '/room' , {
2020-09-28 18:52:54 +02:00
/* Options */
//compression: uWS.SHARED_COMPRESSOR,
maxPayloadLength : 16 * 1024 * 1024 ,
2020-09-30 12:16:39 +02:00
maxBackpressure : 65536 , // Maximum 64kB of data in the buffer.
2020-09-29 16:01:22 +02:00
//idleTimeout: 10,
2020-09-30 10:12:40 +02:00
upgrade : ( res , req , context ) = > {
2020-09-30 12:16:39 +02:00
//console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!');
2020-09-30 10:12:40 +02:00
( async ( ) = > {
/* Keep track of abortions */
const upgradeAborted = { aborted : false } ;
res . onAborted ( ( ) = > {
/* We can simply signal that we were aborted */
upgradeAborted . aborted = true ;
2020-07-27 22:36:07 +02:00
} ) ;
2020-04-04 12:42:02 +02:00
2020-09-30 10:12:40 +02:00
try {
2020-10-09 16:18:25 +02:00
const url = req . getUrl ( ) ;
2020-10-06 15:37:00 +02:00
const query = parse ( req . getQuery ( ) ) ;
2020-10-09 16:18:25 +02:00
const websocketKey = req . getHeader ( 'sec-websocket-key' ) ;
const websocketProtocol = req . getHeader ( 'sec-websocket-protocol' ) ;
const websocketExtensions = req . getHeader ( 'sec-websocket-extensions' ) ;
2020-10-06 15:37:00 +02:00
2020-10-12 16:23:07 +02:00
const roomId = query . roomId ;
2020-10-13 16:46:46 +02:00
//todo: better validation: /\/_\/.*\/.*/ or /\/@\/.*\/.*\/.*/
2020-10-12 16:23:07 +02:00
if ( typeof roomId !== 'string' ) {
throw new Error ( 'Undefined room ID: ' ) ;
}
const roomIdentifier = new RoomIdentifier ( roomId ) ;
2020-10-06 18:09:23 +02:00
2020-10-06 15:37:00 +02:00
const token = query . token ;
2020-10-06 18:09:23 +02:00
const x = Number ( query . x ) ;
const y = Number ( query . y ) ;
const top = Number ( query . top ) ;
const bottom = Number ( query . bottom ) ;
const left = Number ( query . left ) ;
const right = Number ( query . right ) ;
const name = query . name ;
if ( typeof name !== 'string' ) {
throw new Error ( 'Expecting name' ) ;
}
if ( name === '' ) {
throw new Error ( 'No empty name' ) ;
}
let characterLayers = query . characterLayers ;
if ( characterLayers === null ) {
throw new Error ( 'Expecting skin' ) ;
}
if ( typeof characterLayers === 'string' ) {
characterLayers = [ characterLayers ] ;
}
2020-10-06 15:37:00 +02:00
2020-10-09 14:53:18 +02:00
const userUuid = await jwtTokenManager . getUserUuidFromToken ( token ) ;
2020-10-09 16:18:25 +02:00
console . log ( 'uuid' , userUuid ) ;
2020-10-09 14:53:18 +02:00
2020-10-12 16:23:07 +02:00
if ( roomIdentifier . anonymous === false ) {
const isGranted = await adminApi . memberIsGrantedAccessToRoom ( userUuid , roomIdentifier ) ;
if ( ! isGranted ) {
console . log ( 'access not granted for user ' + userUuid + ' and room ' + roomId ) ;
throw new Error ( 'Client cannot acces this ressource.' )
} else {
console . log ( 'access granted for user ' + userUuid + ' and room ' + roomId ) ;
2020-10-13 16:46:46 +02:00
}
2020-10-09 14:53:18 +02:00
}
2020-09-15 16:21:41 +02:00
2020-09-30 10:12:40 +02:00
if ( upgradeAborted . aborted ) {
console . log ( "Ouch! Client disconnected before we could upgrade it!" ) ;
/* You must not upgrade now */
return ;
}
2020-09-15 16:21:41 +02:00
2020-09-30 10:12:40 +02:00
/* This immediately calls open handler, you must not use res after this call */
res . upgrade ( {
// Data passed here is accessible on the "websocket" socket object.
2020-10-09 16:18:25 +02:00
url ,
2020-10-06 15:37:00 +02:00
token ,
2020-10-06 18:09:23 +02:00
userUuid ,
roomId ,
name ,
characterLayers ,
position : {
x : x ,
y : y ,
direction : 'down' ,
moving : false
} as PointInterface ,
viewport : {
top ,
right ,
bottom ,
left
}
2020-09-30 10:12:40 +02:00
} ,
/* Spell these correctly */
2020-10-09 16:18:25 +02:00
websocketKey ,
websocketProtocol ,
websocketExtensions ,
2020-09-30 10:12:40 +02:00
context ) ;
2020-09-30 10:17:01 +02:00
} catch ( e ) {
2020-09-30 10:12:40 +02:00
if ( e instanceof Error ) {
2020-10-09 16:18:25 +02:00
console . log ( e . message ) ;
2020-09-30 10:12:40 +02:00
res . writeStatus ( "401 Unauthorized" ) . end ( e . message ) ;
} else {
2020-10-09 16:18:25 +02:00
console . log ( e ) ;
2020-09-30 10:12:40 +02:00
res . writeStatus ( "500 Internal Server Error" ) . end ( 'An error occurred' ) ;
}
2020-09-15 16:21:41 +02:00
return ;
}
2020-09-30 10:12:40 +02:00
} ) ( ) ;
} ,
2020-09-28 18:52:54 +02:00
/* Handlers */
open : ( ws ) = > {
const client : ExSocketInterface = ws as ExSocketInterface ;
2020-09-30 10:12:40 +02:00
client . userId = this . nextUserId ;
this . nextUserId ++ ;
client . userUuid = ws . userUuid ;
client . token = ws . token ;
2020-09-28 18:52:54 +02:00
client . batchedMessages = new BatchMessage ( ) ;
client . batchTimeout = null ;
client . emitInBatch = ( payload : SubMessage ) : void = > {
emitInBatch ( client , payload ) ;
2020-09-15 16:21:41 +02:00
}
2020-09-28 18:52:54 +02:00
client . disconnecting = false ;
2020-10-06 18:09:23 +02:00
client . name = ws . name ;
client . characterLayers = ws . characterLayers ;
client . roomId = ws . roomId ;
2020-09-28 18:52:54 +02:00
this . sockets . set ( client . userId , client ) ;
// Let's log server load when a user joins
this . nbClientsGauge . inc ( ) ;
console . log ( new Date ( ) . toISOString ( ) + ' A user joined (' , this . sockets . size , ' connected users)' ) ;
2020-10-06 18:09:23 +02:00
// Let's join the room
this . handleJoinRoom ( client , client . roomId , client . position , client . viewport , client . name , client . characterLayers ) ;
2020-09-28 18:52:54 +02:00
} ,
2020-10-06 15:37:00 +02:00
message : ( ws , arrayBuffer , isBinary ) : void = > {
2020-09-28 18:52:54 +02:00
const client = ws as ExSocketInterface ;
const message = ClientToServerMessage . deserializeBinary ( new Uint8Array ( arrayBuffer ) ) ;
2020-10-06 15:37:00 +02:00
if ( message . hasViewportmessage ( ) ) {
2020-09-28 18:52:54 +02:00
this . handleViewport ( client , message . getViewportmessage ( ) as ViewportMessage ) ;
} else if ( message . hasUsermovesmessage ( ) ) {
this . handleUserMovesMessage ( client , message . getUsermovesmessage ( ) as UserMovesMessage ) ;
} else if ( message . hasSetplayerdetailsmessage ( ) ) {
this . handleSetPlayerDetails ( client , message . getSetplayerdetailsmessage ( ) as SetPlayerDetailsMessage ) ;
} else if ( message . hasSilentmessage ( ) ) {
this . handleSilentMessage ( client , message . getSilentmessage ( ) as SilentMessage ) ;
} else if ( message . hasItemeventmessage ( ) ) {
this . handleItemEvent ( client , message . getItemeventmessage ( ) as ItemEventMessage ) ;
2020-09-29 16:01:22 +02:00
} else if ( message . hasWebrtcsignaltoservermessage ( ) ) {
this . emitVideo ( client , message . getWebrtcsignaltoservermessage ( ) as WebRtcSignalToServerMessage )
} else if ( message . hasWebrtcscreensharingsignaltoservermessage ( ) ) {
this . emitScreenSharing ( client , message . getWebrtcscreensharingsignaltoservermessage ( ) as WebRtcSignalToServerMessage )
2020-10-01 14:11:34 +02:00
} else if ( message . hasPlayglobalmessage ( ) ) {
this . emitPlayGlobalMessage ( client , message . getPlayglobalmessage ( ) as PlayGlobalMessage )
2020-04-04 17:22:02 +02:00
}
2020-06-11 23:18:06 +02:00
2020-09-28 18:52:54 +02:00
/* Ok is false if backpressure was built up, wait for drain */
//let ok = ws.send(message, isBinary);
} ,
drain : ( ws ) = > {
console . log ( 'WebSocket backpressure: ' + ws . getBufferedAmount ( ) ) ;
} ,
close : ( ws , code , message ) = > {
const Client = ( ws as ExSocketInterface ) ;
2020-05-12 11:49:55 +02:00
try {
2020-09-28 18:52:54 +02:00
Client . disconnecting = true ;
2020-05-12 11:49:55 +02:00
//leave room
this . leaveRoom ( Client ) ;
//delete all socket information
2020-09-29 17:24:16 +02:00
/ * d e l e t e C l i e n t . r o o m I d ;
2020-05-12 11:49:55 +02:00
delete Client . token ;
2020-09-29 17:24:16 +02:00
delete Client . position ; * /
2020-05-12 11:49:55 +02:00
} catch ( e ) {
console . error ( 'An error occurred on "disconnect"' ) ;
console . error ( e ) ;
}
2020-09-28 18:52:54 +02:00
2020-05-23 15:04:25 +02:00
this . sockets . delete ( Client . userId ) ;
2020-05-28 22:00:56 +02:00
// Let's log server load when a user leaves
2020-06-29 23:01:52 +02:00
this . nbClientsGauge . dec ( ) ;
2020-09-28 18:52:54 +02:00
console . log ( 'A user left (' , this . sockets . size , ' connected users)' ) ;
}
} )
// TODO: finish this!
/ * t h i s . I o . o n ( S o c k e t I o E v e n t . C O N N E C T I O N , ( s o c k e t : S o c k e t ) = > {
socket . on ( SocketIoEvent . WEBRTC_SIGNAL , ( data : unknown ) = > {
this . emitVideo ( ( socket as ExSocketInterface ) , data ) ;
2020-04-29 01:40:32 +02:00
} ) ;
2020-05-14 23:19:48 +02:00
2020-09-28 18:52:54 +02:00
socket . on ( SocketIoEvent . WEBRTC_SCREEN_SHARING_SIGNAL , ( data : unknown ) = > {
this . emitScreenSharing ( ( socket as ExSocketInterface ) , data ) ;
2020-05-15 22:40:06 +02:00
} ) ;
2020-08-31 14:03:40 +02:00
2020-09-28 18:52:54 +02:00
} ) ; * /
}
2020-08-31 14:03:40 +02:00
2020-09-28 18:52:54 +02:00
private emitError ( Client : ExSocketInterface , message : string ) : void {
const errorMessage = new ErrorMessage ( ) ;
errorMessage . setMessage ( message ) ;
2020-08-31 14:03:40 +02:00
2020-09-28 18:52:54 +02:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setErrormessage ( errorMessage ) ;
2020-07-27 22:36:07 +02:00
2020-09-28 18:52:54 +02:00
if ( ! Client . disconnecting ) {
2020-09-29 16:01:22 +02:00
Client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
2020-09-28 18:52:54 +02:00
}
console . warn ( message ) ;
}
2020-09-24 17:24:37 +02:00
2020-10-09 17:14:03 +02:00
private handleJoinRoom ( client : ExSocketInterface , roomId : string , position : PointInterface , viewport : ViewportInterface , name : string , characterLayers : string [ ] ) : void {
2020-09-28 18:52:54 +02:00
try {
//join new previous room
2020-10-09 17:14:03 +02:00
const gameRoom = this . joinRoom ( client , roomId , position ) ;
2020-07-27 22:36:07 +02:00
2020-10-06 18:09:23 +02:00
const things = gameRoom . setViewport ( client , viewport ) ;
2020-09-24 17:24:37 +02:00
2020-09-28 18:52:54 +02:00
const roomJoinedMessage = new RoomJoinedMessage ( ) ;
2020-09-24 17:24:37 +02:00
2020-09-28 18:52:54 +02:00
for ( const thing of things ) {
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-07-27 22:36:07 +02:00
}
2020-09-24 17:24:37 +02:00
2020-09-28 18:52:54 +02:00
const userJoinedMessage = new UserJoinedMessage ( ) ;
userJoinedMessage . setUserid ( thing . id ) ;
userJoinedMessage . setName ( player . name ) ;
userJoinedMessage . setCharacterlayersList ( player . characterLayers ) ;
userJoinedMessage . setPosition ( ProtobufUtils . toPositionMessage ( player . position ) ) ;
roomJoinedMessage . addUser ( userJoinedMessage ) ;
} else if ( thing instanceof Group ) {
const groupUpdateMessage = new GroupUpdateMessage ( ) ;
groupUpdateMessage . setGroupid ( thing . getId ( ) ) ;
groupUpdateMessage . setPosition ( ProtobufUtils . toPointMessage ( thing . getPosition ( ) ) ) ;
roomJoinedMessage . addGroup ( groupUpdateMessage ) ;
} else {
console . error ( "Unexpected type for Movable returned by setViewport" ) ;
2020-07-27 22:36:07 +02:00
}
2020-09-28 18:52:54 +02:00
}
2020-09-19 01:08:56 +02:00
2020-10-06 15:37:00 +02:00
for ( const [ itemId , item ] of gameRoom . getItemsState ( ) . entries ( ) ) {
2020-09-28 18:52:54 +02:00
const itemStateMessage = new ItemStateMessage ( ) ;
itemStateMessage . setItemid ( itemId ) ;
itemStateMessage . setStatejson ( JSON . stringify ( item ) ) ;
roomJoinedMessage . addItem ( itemStateMessage ) ;
}
2020-10-13 16:46:46 +02:00
roomJoinedMessage . setCurrentuserid ( client . userId ) ;
2020-09-28 18:52:54 +02:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setRoomjoinedmessage ( roomJoinedMessage ) ;
2020-10-06 15:37:00 +02:00
if ( ! client . disconnecting ) {
client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
2020-09-28 18:52:54 +02:00
}
} catch ( e ) {
console . error ( 'An error occurred on "join_room" event' ) ;
console . error ( e ) ;
}
}
private handleViewport ( client : ExSocketInterface , viewportMessage : ViewportMessage ) {
try {
const viewport = viewportMessage . toObject ( ) ;
client . viewport = viewport ;
const world = this . Worlds . get ( client . roomId ) ;
if ( ! world ) {
console . error ( "In SET_VIEWPORT, could not find world with id '" , client . roomId , "'" ) ;
return ;
}
world . setViewport ( client , client . viewport ) ;
} catch ( e ) {
console . error ( 'An error occurred on "SET_VIEWPORT" event' ) ;
console . error ( e ) ;
}
}
private handleUserMovesMessage ( client : ExSocketInterface , userMovesMessage : UserMovesMessage ) {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try {
const userMoves = userMovesMessage . toObject ( ) ;
2020-09-30 12:50:34 +02:00
// If CPU is high, let's drop messages of users moving (we will only dispatch the final position)
2020-09-30 14:42:35 +02:00
if ( cpuTracker . isOverHeating ( ) && userMoves . position ? . moving === true ) {
2020-09-30 12:50:34 +02:00
return ;
}
2020-09-28 18:52:54 +02:00
const position = userMoves . position ;
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' ) ;
}
let direction : string ;
switch ( position . direction ) {
case Direction . UP :
direction = 'up' ;
break ;
case Direction . DOWN :
direction = 'down' ;
break ;
case Direction . LEFT :
direction = 'left' ;
break ;
case Direction . RIGHT :
direction = 'right' ;
break ;
default :
throw new Error ( "Unexpected direction" ) ;
}
// sending to all clients in room except sender
client . position = {
x : position.x ,
y : position.y ,
direction ,
moving : position.moving ,
} ;
client . viewport = viewport ;
// update position in the world
const world = this . Worlds . get ( client . roomId ) ;
if ( ! world ) {
console . error ( "In USER_POSITION, could not find world with id '" , client . roomId , "'" ) ;
return ;
}
world . updatePosition ( client , client . position ) ;
world . setViewport ( client , client . viewport ) ;
} catch ( e ) {
console . error ( 'An error occurred on "user_position" event' ) ;
console . error ( e ) ;
}
2020-04-29 01:40:32 +02:00
}
2020-10-06 18:09:23 +02:00
// Useless now, will be useful again if we allow editing details in game
2020-09-28 18:52:54 +02:00
private handleSetPlayerDetails ( client : ExSocketInterface , playerDetailsMessage : SetPlayerDetailsMessage ) {
const playerDetails = {
name : playerDetailsMessage.getName ( ) ,
characterLayers : playerDetailsMessage.getCharacterlayersList ( )
} ;
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if ( ! isSetPlayerDetailsMessage ( playerDetails ) ) {
this . emitError ( client , 'Invalid SET_PLAYER_DETAILS message received: ' ) ;
2020-06-11 23:18:06 +02:00
return ;
}
2020-09-28 18:52:54 +02:00
client . name = playerDetails . name ;
client . characterLayers = playerDetails . characterLayers ;
}
private handleSilentMessage ( client : ExSocketInterface , silentMessage : SilentMessage ) {
try {
// update position in the world
const world = this . Worlds . get ( client . roomId ) ;
if ( ! world ) {
console . error ( "In handleSilentMessage, could not find world with id '" , client . roomId , "'" ) ;
return ;
}
world . setSilent ( client , silentMessage . getSilent ( ) ) ;
} catch ( e ) {
console . error ( 'An error occurred on "handleSilentMessage"' ) ;
console . error ( e ) ;
}
}
private handleItemEvent ( ws : ExSocketInterface , itemEventMessage : ItemEventMessage ) {
const itemEvent = ProtobufUtils . toItemEvent ( itemEventMessage ) ;
try {
const world = this . Worlds . get ( ws . roomId ) ;
if ( ! world ) {
console . error ( "Could not find world with id '" , ws . roomId , "'" ) ;
return ;
}
const subMessage = new SubMessage ( ) ;
subMessage . setItemeventmessage ( itemEventMessage ) ;
// Let's send the event without using the SocketIO room.
for ( const user of world . getUsers ( ) . values ( ) ) {
const client = this . searchClientByIdOrFail ( user . id ) ;
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
emitInBatch ( client , subMessage ) ;
}
world . setItemState ( itemEvent . itemId , itemEvent . state ) ;
} catch ( e ) {
console . error ( 'An error occurred on "item_event"' ) ;
console . error ( e ) ;
}
2020-04-29 01:40:32 +02:00
}
2020-09-29 16:01:22 +02:00
emitVideo ( socket : ExSocketInterface , data : WebRtcSignalToServerMessage ) : void {
2020-06-11 23:18:06 +02:00
//send only at user
2020-09-29 16:01:22 +02:00
const client = this . sockets . get ( data . getReceiverid ( ) ) ;
2020-06-11 23:18:06 +02:00
if ( client === undefined ) {
2020-09-29 16:01:22 +02:00
console . warn ( "While exchanging a WebRTC signal: client with id " , data . getReceiverid ( ) , " does not exist. This might be a race condition." ) ;
2020-06-11 23:18:06 +02:00
return ;
}
2020-09-29 16:01:22 +02:00
const webrtcSignalToClient = new WebRtcSignalToClientMessage ( ) ;
webrtcSignalToClient . setUserid ( socket . userId ) ;
webrtcSignalToClient . setSignal ( data . getSignal ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setWebrtcsignaltoclientmessage ( webrtcSignalToClient ) ;
if ( ! client . disconnecting ) {
client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
2020-06-14 14:47:16 +02:00
}
2020-09-29 16:01:22 +02:00
}
emitScreenSharing ( socket : ExSocketInterface , data : WebRtcSignalToServerMessage ) : void {
2020-08-20 00:05:00 +02:00
//send only at user
2020-09-29 16:01:22 +02:00
const client = this . sockets . get ( data . getReceiverid ( ) ) ;
2020-08-20 00:05:00 +02:00
if ( client === undefined ) {
2020-09-29 16:01:22 +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." ) ;
2020-08-20 00:05:00 +02:00
return ;
2020-06-14 20:53:18 +02:00
}
2020-09-29 16:01:22 +02:00
const webrtcSignalToClient = new WebRtcSignalToClientMessage ( ) ;
webrtcSignalToClient . setUserid ( socket . userId ) ;
webrtcSignalToClient . setSignal ( data . getSignal ( ) ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setWebrtcscreensharingsignaltoclientmessage ( webrtcSignalToClient ) ;
if ( ! client . disconnecting ) {
client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
}
2020-06-14 14:47:16 +02:00
}
2020-09-18 13:57:38 +02:00
searchClientByIdOrFail ( userId : number ) : ExSocketInterface {
2020-06-09 15:54:54 +02:00
const client : ExSocketInterface | undefined = this . sockets . get ( userId ) ;
2020-05-18 18:33:04 +02:00
if ( client === undefined ) {
throw new Error ( "Could not find user with id " + userId ) ;
2020-05-08 21:17:52 +02:00
}
2020-05-18 18:33:04 +02:00
return client ;
2020-05-03 16:28:18 +02:00
}
2020-05-10 19:54:41 +02:00
leaveRoom ( Client : ExSocketInterface ) {
2020-05-19 19:11:12 +02:00
// leave previous room and world
2020-05-10 19:54:41 +02:00
if ( Client . roomId ) {
2020-06-29 22:56:41 +02:00
try {
//user leave previous world
2020-10-06 15:37:00 +02:00
const world : GameRoom | undefined = this . Worlds . get ( Client . roomId ) ;
2020-06-29 22:56:41 +02:00
if ( world ) {
world . leave ( Client ) ;
if ( world . isEmpty ( ) ) {
this . Worlds . delete ( Client . roomId ) ;
}
2020-06-29 19:14:54 +02:00
}
2020-06-29 22:56:41 +02:00
//user leave previous room
2020-09-28 18:52:54 +02:00
//Client.leave(Client.roomId);
2020-06-29 22:56:41 +02:00
} finally {
2020-06-29 23:01:52 +02:00
this . nbClientsPerRoomGauge . dec ( { room : Client.roomId } ) ;
2020-09-29 17:24:16 +02:00
//delete Client.roomId;
2020-05-10 19:54:41 +02:00
}
}
}
2020-05-15 23:24:04 +02:00
2020-10-06 15:37:00 +02:00
private joinRoom ( client : ExSocketInterface , roomId : string , position : PointInterface ) : GameRoom {
2020-10-06 18:09:23 +02:00
2020-05-10 19:54:41 +02:00
//join user in room
2020-06-29 23:01:52 +02:00
this . nbClientsPerRoomGauge . inc ( { room : roomId } ) ;
2020-10-06 15:37:00 +02:00
client . roomId = roomId ;
client . position = position ;
2020-05-10 19:54:41 +02:00
//check and create new world for a room
2020-05-19 19:11:12 +02:00
let world = this . Worlds . get ( roomId )
if ( world === undefined ) {
2020-10-06 15:37:00 +02:00
world = new GameRoom ( ( user1 : User , group : Group ) = > {
2020-09-29 16:01:22 +02:00
this . joinWebRtcRoom ( user1 , group ) ;
} , ( user1 : User , group : Group ) = > {
2020-05-10 19:54:41 +02:00
this . disConnectedUser ( user1 , group ) ;
2020-09-16 16:13:47 +02:00
} , MINIMUM_DISTANCE , GROUP_RADIUS , ( thing : Movable , listener : User ) = > {
2020-09-15 16:21:41 +02:00
const clientListener = this . searchClientByIdOrFail ( listener . id ) ;
2020-09-16 16:06:43 +02:00
if ( thing instanceof User ) {
const clientUser = this . searchClientByIdOrFail ( thing . id ) ;
2020-09-24 14:50:28 +02:00
const userJoinedMessage = new UserJoinedMessage ( ) ;
2020-09-28 18:52:54 +02:00
if ( ! Number . isInteger ( clientUser . userId ) ) {
throw new Error ( 'clientUser.userId is not an integer ' + clientUser . userId ) ;
}
2020-09-24 14:50:28 +02:00
userJoinedMessage . setUserid ( clientUser . userId ) ;
userJoinedMessage . setName ( clientUser . name ) ;
userJoinedMessage . setCharacterlayersList ( clientUser . characterLayers ) ;
userJoinedMessage . setPosition ( ProtobufUtils . toPositionMessage ( clientUser . position ) ) ;
const subMessage = new SubMessage ( ) ;
subMessage . setUserjoinedmessage ( userJoinedMessage ) ;
2020-09-28 18:52:54 +02:00
emitInBatch ( clientListener , subMessage ) ;
2020-09-16 16:06:43 +02:00
} else if ( thing instanceof Group ) {
2020-09-21 11:24:03 +02:00
this . emitCreateUpdateGroupEvent ( clientListener , thing ) ;
2020-09-16 16:06:43 +02:00
} else {
console . error ( 'Unexpected type for Movable.' ) ;
}
} , ( thing : Movable , position , listener ) = > {
2020-09-15 16:21:41 +02:00
const clientListener = this . searchClientByIdOrFail ( listener . id ) ;
2020-09-16 16:06:43 +02:00
if ( thing instanceof User ) {
const clientUser = this . searchClientByIdOrFail ( thing . id ) ;
2020-09-18 15:51:15 +02:00
const userMovedMessage = new UserMovedMessage ( ) ;
userMovedMessage . setUserid ( clientUser . userId ) ;
2020-09-18 18:16:26 +02:00
userMovedMessage . setPosition ( ProtobufUtils . toPositionMessage ( clientUser . position ) ) ;
2020-09-18 15:51:15 +02:00
const subMessage = new SubMessage ( ) ;
subMessage . setUsermovedmessage ( userMovedMessage ) ;
2020-09-28 18:52:54 +02:00
clientListener . emitInBatch ( subMessage ) ;
2020-09-16 16:06:43 +02:00
//console.log("Sending USER_MOVED event");
} else if ( thing instanceof Group ) {
2020-09-21 11:24:03 +02:00
this . emitCreateUpdateGroupEvent ( clientListener , thing ) ;
2020-09-16 16:06:43 +02:00
} else {
console . error ( 'Unexpected type for Movable.' ) ;
}
} , ( thing : Movable , listener ) = > {
2020-09-15 16:21:41 +02:00
const clientListener = this . searchClientByIdOrFail ( listener . id ) ;
2020-09-16 16:06:43 +02:00
if ( thing instanceof User ) {
const clientUser = this . searchClientByIdOrFail ( thing . id ) ;
2020-09-24 16:11:47 +02:00
this . emitUserLeftEvent ( clientListener , clientUser . userId ) ;
2020-09-16 16:06:43 +02:00
} else if ( thing instanceof Group ) {
2020-09-24 10:05:16 +02:00
this . emitDeleteGroupEvent ( clientListener , thing . getId ( ) ) ;
2020-09-16 16:06:43 +02:00
} else {
console . error ( 'Unexpected type for Movable.' ) ;
}
2020-09-15 16:21:41 +02:00
2020-05-10 19:54:41 +02:00
} ) ;
2020-05-15 23:24:04 +02:00
this . Worlds . set ( roomId , world ) ;
2020-05-10 19:54:41 +02:00
}
2020-05-19 19:11:12 +02:00
// Dispatch groups position to newly connected user
world . getGroups ( ) . forEach ( ( group : Group ) = > {
2020-10-06 15:37:00 +02:00
this . emitCreateUpdateGroupEvent ( client , group ) ;
2020-05-19 19:11:12 +02:00
} ) ;
//join world
2020-10-06 15:37:00 +02:00
world . join ( client , client . position ) ;
2020-05-19 19:11:12 +02:00
return world ;
2020-05-10 19:54:41 +02:00
}
2020-09-28 18:52:54 +02:00
private emitCreateUpdateGroupEvent ( client : ExSocketInterface , group : Group ) : void {
2020-09-21 11:24:03 +02:00
const position = group . getPosition ( ) ;
const pointMessage = new PointMessage ( ) ;
pointMessage . setX ( Math . floor ( position . x ) ) ;
pointMessage . setY ( Math . floor ( position . y ) ) ;
const groupUpdateMessage = new GroupUpdateMessage ( ) ;
groupUpdateMessage . setGroupid ( group . getId ( ) ) ;
groupUpdateMessage . setPosition ( pointMessage ) ;
2020-09-24 10:05:16 +02:00
const subMessage = new SubMessage ( ) ;
subMessage . setGroupupdatemessage ( groupUpdateMessage ) ;
2020-09-28 18:52:54 +02:00
emitInBatch ( client , subMessage ) ;
2020-09-24 10:05:16 +02:00
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
}
2020-09-29 10:57:14 +02:00
private emitDeleteGroupEvent ( client : ExSocketInterface , groupId : number ) : void {
2020-09-24 10:05:16 +02:00
const groupDeleteMessage = new GroupDeleteMessage ( ) ;
groupDeleteMessage . setGroupid ( groupId ) ;
const subMessage = new SubMessage ( ) ;
subMessage . setGroupdeletemessage ( groupDeleteMessage ) ;
2020-09-28 18:52:54 +02:00
emitInBatch ( client , subMessage ) ;
2020-09-21 11:24:03 +02:00
}
2020-09-29 10:57:14 +02:00
private emitUserLeftEvent ( client : ExSocketInterface , userId : number ) : void {
2020-09-24 16:11:47 +02:00
const userLeftMessage = new UserLeftMessage ( ) ;
userLeftMessage . setUserid ( userId ) ;
const subMessage = new SubMessage ( ) ;
subMessage . setUserleftmessage ( userLeftMessage ) ;
2020-09-28 18:52:54 +02:00
emitInBatch ( client , subMessage ) ;
2020-09-24 16:11:47 +02:00
}
2020-09-29 16:01:22 +02:00
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 ) {
2020-04-29 01:40:32 +02:00
return ;
2020-09-29 16:01:22 +02:00
} * /
2020-09-28 18:52:54 +02:00
2020-09-29 16:01:22 +02:00
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 . setName ( otherUser . socket . name ) ;
webrtcStartMessage1 . setInitiator ( true ) ;
const serverToClientMessage1 = new ServerToClientMessage ( ) ;
serverToClientMessage1 . setWebrtcstartmessage ( webrtcStartMessage1 ) ;
if ( ! user . socket . disconnecting ) {
user . socket . send ( serverToClientMessage1 . serializeBinary ( ) . buffer , true ) ;
2020-09-29 17:24:16 +02:00
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
2020-09-29 16:01:22 +02:00
}
const webrtcStartMessage2 = new WebRtcStartMessage ( ) ;
webrtcStartMessage2 . setUserid ( user . id ) ;
webrtcStartMessage2 . setName ( user . socket . name ) ;
webrtcStartMessage2 . setInitiator ( false ) ;
const serverToClientMessage2 = new ServerToClientMessage ( ) ;
serverToClientMessage2 . setWebrtcstartmessage ( webrtcStartMessage2 ) ;
if ( ! otherUser . socket . disconnecting ) {
otherUser . socket . send ( serverToClientMessage2 . serializeBinary ( ) . buffer , true ) ;
2020-09-29 17:24:16 +02:00
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
2020-09-29 16:01:22 +02:00
}
2020-09-28 18:52:54 +02:00
2020-04-29 01:40:32 +02:00
}
2020-09-29 16:01:22 +02:00
/ * s o c k e t . j o i n ( r o o m I d ) ;
2020-04-29 01:40:32 +02:00
socket . webRtcRoomId = roomId ;
2020-05-13 09:39:48 +02:00
//if two persons in room share
2020-09-29 09:45:47 +02:00
if ( this . Io . sockets . adapter . rooms [ roomId ] . length < 2 ) {
2020-04-29 01:40:32 +02:00
return ;
}
2020-08-20 00:05:00 +02:00
// TODO: scanning all sockets is maybe not the most efficient
2020-06-09 15:54:54 +02:00
const clients : Array < ExSocketInterface > = ( Object . values ( this . Io . sockets . sockets ) as Array < ExSocketInterface > )
2020-05-08 11:54:47 +02:00
. filter ( ( client : ExSocketInterface ) = > client . webRtcRoomId && client . webRtcRoomId === roomId ) ;
2020-04-29 01:40:32 +02:00
//send start at one client to initialise offer webrtc
//send all users in room to create PeerConnection in front
clients . forEach ( ( client : ExSocketInterface , index : number ) = > {
2020-08-20 00:05:00 +02:00
const peerClients = clients . reduce ( ( tabs : Array < UserInGroupInterface > , clientId : ExSocketInterface , indexClientId : number ) = > {
2020-05-23 15:04:25 +02:00
if ( ! clientId . userId || clientId . userId === client . userId ) {
2020-04-29 01:40:32 +02:00
return tabs ;
}
tabs . push ( {
2020-05-23 15:04:25 +02:00
userId : clientId.userId ,
2020-05-14 20:39:30 +02:00
name : clientId.name ,
2020-04-29 01:40:32 +02:00
initiator : index <= indexClientId
} ) ;
return tabs ;
} , [ ] ) ;
2020-09-18 13:57:38 +02:00
client . emit ( SocketIoEvent . WEBRTC_START , { clients : peerClients , roomId : roomId } ) ;
2020-09-29 09:45:47 +02:00
} ) ; * /
2020-04-04 04:08:12 +02:00
}
2020-04-04 16:25:03 +02:00
2020-04-04 19:25:08 +02:00
/ * * p e r m i t t o s h a r e u s e r p o s i t i o n
2020-05-03 16:28:18 +02:00
* * users position will send in event 'user-position'
* * The data sent is an array with information for each user :
[
{
2020-04-04 19:25:08 +02:00
userId : < string > ,
roomId : < string > ,
position : {
x : < number > ,
2020-04-07 21:03:33 +02:00
y : < number > ,
direction : < string >
2020-04-04 19:25:08 +02:00
}
} ,
2020-05-03 16:28:18 +02:00
. . .
]
2020-04-04 19:25:08 +02:00
* * /
2020-04-27 00:44:25 +02:00
2020-05-08 00:35:36 +02:00
//disconnect user
2020-09-29 16:01:22 +02:00
disConnectedUser ( user : User , group : Group ) {
2020-06-05 13:07:18 +02:00
// 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.
2020-09-29 16:01:22 +02:00
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 ) ;
if ( ! otherUser . socket . disconnecting ) {
otherUser . socket . send ( serverToClientMessage1 . serializeBinary ( ) . buffer , true ) ;
}
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage ( ) ;
webrtcDisconnectMessage2 . setUserid ( otherUser . id ) ;
const serverToClientMessage2 = new ServerToClientMessage ( ) ;
serverToClientMessage2 . setWebrtcdisconnectmessage ( webrtcDisconnectMessage2 ) ;
if ( ! user . socket . disconnecting ) {
user . socket . send ( serverToClientMessage2 . serializeBinary ( ) . buffer , true ) ;
}
2020-06-05 13:07:18 +02:00
}
2020-05-18 18:33:04 +02:00
//disconnect webrtc room
2020-09-29 16:01:22 +02:00
/ * i f ( ! C l i e n t . w e b R t c R o o m I d ) {
2020-05-03 16:28:18 +02:00
return ;
2020-09-29 16:01:22 +02:00
} * /
//Client.leave(Client.webRtcRoomId);
//delete Client.webRtcRoomId;
2020-04-27 00:44:25 +02:00
}
2020-09-25 13:48:02 +02:00
2020-10-01 14:11:34 +02:00
private emitPlayGlobalMessage ( client : ExSocketInterface , playglobalmessage : PlayGlobalMessage ) {
try {
const world = this . Worlds . get ( client . roomId ) ;
if ( ! world ) {
console . error ( "In emitPlayGlobalMessage, could not find world with id '" , client . roomId , "'" ) ;
return ;
}
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setPlayglobalmessage ( playglobalmessage ) ;
for ( const [ id , user ] of world . getUsers ( ) . entries ( ) ) {
user . socket . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
}
} catch ( e ) {
console . error ( 'An error occurred on "emitPlayGlobalMessage" event' ) ;
console . error ( e ) ;
2020-05-03 16:28:18 +02:00
}
2020-10-01 14:11:34 +02:00
}
2020-10-06 15:37:00 +02:00
public getWorlds ( ) : Map < string , GameRoom > {
2020-09-25 13:48:02 +02:00
return this . Worlds ;
2020-04-27 00:44:25 +02:00
}
2020-04-04 22:35:20 +02:00
}