2020-11-13 18:00:22 +01:00
import { PusherRoom } from "../Model/PusherRoom" ;
import { CharacterLayer , ExSocketInterface } from "../Model/Websocket/ExSocketInterface" ;
import {
GroupDeleteMessage ,
ItemEventMessage ,
PlayGlobalMessage ,
RoomJoinedMessage ,
ServerToClientMessage ,
SetPlayerDetailsMessage ,
SilentMessage ,
SubMessage ,
ReportPlayerMessage ,
2020-12-10 17:46:15 +01:00
UserLeftMessage ,
2020-11-13 18:00:22 +01:00
UserMovesMessage ,
2020-12-10 17:46:15 +01:00
ViewportMessage ,
2020-11-13 18:00:22 +01:00
WebRtcSignalToServerMessage ,
QueryJitsiJwtMessage ,
SendJitsiJwtMessage ,
2020-12-10 17:46:15 +01:00
JoinRoomMessage ,
CharacterLayerMessage ,
PusherToBackMessage ,
2021-03-11 16:14:34 +01:00
WorldFullMessage ,
2020-12-10 17:46:15 +01:00
AdminPusherToBackMessage ,
2021-01-17 03:07:46 +01:00
ServerToAdminClientMessage ,
2021-03-11 16:14:34 +01:00
UserJoinedRoomMessage , UserLeftRoomMessage , AdminMessage , BanMessage
2020-11-13 18:00:22 +01:00
} from "../Messages/generated/messages_pb" ;
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils" ;
2021-03-05 18:25:27 +01:00
import { JITSI_ISS , SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable" ;
2020-11-13 18:00:22 +01:00
import { adminApi , CharacterTexture } from "./AdminApi" ;
2021-03-11 16:14:34 +01:00
import { emitInBatch } from "./IoSocketHelpers" ;
2020-11-13 18:00:22 +01:00
import Jwt from "jsonwebtoken" ;
import { JITSI_URL } from "../Enum/EnvironmentVariable" ;
import { clientEventsEmitter } from "./ClientEventsEmitter" ;
import { gaugeManager } from "./GaugeManager" ;
import { apiClientRepository } from "./ApiClientRepository" ;
import { GroupDescriptor , UserDescriptor , ZoneEventListener } from "_Model/Zone" ;
import Debug from "debug" ;
2020-12-10 17:46:15 +01:00
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface" ;
2021-03-11 16:14:34 +01:00
import { WebSocket } from "uWebSockets.js" ;
2020-11-13 18:00:22 +01:00
const debug = Debug ( 'socket' ) ;
interface AdminSocketRoomsList {
[ index : string ] : number ;
}
interface AdminSocketUsersList {
[ index : string ] : boolean ;
}
export interface AdminSocketData {
rooms : AdminSocketRoomsList ,
users : AdminSocketUsersList ,
}
export class SocketManager implements ZoneEventListener {
2021-03-11 16:14:34 +01:00
2020-11-13 18:00:22 +01:00
private Worlds : Map < string , PusherRoom > = new Map < string , PusherRoom > ( ) ;
private sockets : Map < number , ExSocketInterface > = new Map < number , ExSocketInterface > ( ) ;
constructor ( ) {
clientEventsEmitter . registerToClientJoin ( ( clientUUid : string , roomId : string ) = > {
gaugeManager . incNbClientPerRoomGauge ( roomId ) ;
} ) ;
clientEventsEmitter . registerToClientLeave ( ( clientUUid : string , roomId : string ) = > {
gaugeManager . decNbClientPerRoomGauge ( roomId ) ;
} ) ;
}
2020-12-10 17:46:15 +01:00
async handleAdminRoom ( client : ExAdminSocketInterface , roomId : string ) : Promise < void > {
const apiClient = await apiClientRepository . getClient ( roomId ) ;
const adminRoomStream = apiClient . adminRoom ( ) ;
client . adminConnection = adminRoomStream ;
adminRoomStream . on ( 'data' , ( message : ServerToAdminClientMessage ) = > {
2021-01-18 15:07:40 +01:00
if ( message . hasUserjoinedroom ( ) ) {
const userJoinedRoomMessage = message . getUserjoinedroom ( ) as UserJoinedRoomMessage ;
2020-12-10 17:46:15 +01:00
if ( ! client . disconnecting ) {
2021-01-18 15:07:40 +01:00
client . send ( JSON . stringify ( {
type : 'MemberJoin' ,
data : {
uuid : userJoinedRoomMessage.getUuid ( ) ,
name : userJoinedRoomMessage.getName ( ) ,
ipAddress : userJoinedRoomMessage.getIpaddress ( ) ,
roomId : roomId ,
}
} ) ) ;
2020-12-10 17:46:15 +01:00
}
2021-01-18 15:07:40 +01:00
} else if ( message . hasUserleftroom ( ) ) {
const userLeftRoomMessage = message . getUserleftroom ( ) as UserLeftRoomMessage ;
2020-12-10 17:46:15 +01:00
if ( ! client . disconnecting ) {
2021-01-18 15:07:40 +01:00
client . send ( JSON . stringify ( {
type : 'MemberLeave' ,
data : {
uuid : userLeftRoomMessage.getUuid ( )
}
} ) ) ;
2020-12-10 17:46:15 +01:00
}
} else {
throw new Error ( 'Unexpected admin message' ) ;
}
} ) . on ( 'end' , ( ) = > {
console . warn ( 'Admin connection lost to back server' ) ;
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
if ( ! client . disconnecting ) {
this . closeWebsocketConnection ( client , 1011 , 'Connection lost to back server' ) ;
}
console . log ( 'A user left' ) ;
} ) . on ( 'error' , ( err : Error ) = > {
console . error ( 'Error in connection to back server:' , err ) ;
if ( ! client . disconnecting ) {
this . closeWebsocketConnection ( client , 1011 , 'Error while connecting to back server' ) ;
}
} ) ;
const message = new AdminPusherToBackMessage ( ) ;
message . setSubscribetoroom ( roomId ) ;
adminRoomStream . write ( message ) ;
}
leaveAdminRoom ( socket : ExAdminSocketInterface ) {
if ( socket . adminConnection ) {
socket . adminConnection . end ( ) ;
}
}
2020-11-13 18:00:22 +01:00
getAdminSocketDataFor ( roomId :string ) : AdminSocketData {
throw new Error ( 'Not reimplemented yet' ) ;
/ * c o n s t d a t a : A d m i n S o c k e t D a t a = {
rooms : { } ,
users : { } ,
}
const room = this . Worlds . get ( roomId ) ;
if ( room === undefined ) {
return data ;
}
const users = room . getUsers ( ) ;
data . rooms [ roomId ] = users . size ;
users . forEach ( user = > {
data . users [ user . uuid ] = true
} )
return data ; * /
}
async handleJoinRoom ( client : ExSocketInterface ) : Promise < void > {
const viewport = client . viewport ;
try {
const joinRoomMessage = new JoinRoomMessage ( ) ;
2020-12-02 17:51:46 +01:00
joinRoomMessage . setUseruuid ( client . userUuid ) ;
2021-01-15 03:19:58 +01:00
joinRoomMessage . setIpaddress ( client . IPAddress ) ;
2020-11-13 18:00:22 +01:00
joinRoomMessage . setRoomid ( client . roomId ) ;
joinRoomMessage . setName ( client . name ) ;
joinRoomMessage . setPositionmessage ( ProtobufUtils . toPositionMessage ( client . position ) ) ;
2021-01-17 03:07:46 +01:00
joinRoomMessage . setTagList ( client . tags ) ;
2021-04-02 21:21:11 +02:00
joinRoomMessage . setCompanion ( client . companion ) ;
2020-11-13 18:00:22 +01:00
for ( const characterLayer of client . characterLayers ) {
const characterLayerMessage = new CharacterLayerMessage ( ) ;
characterLayerMessage . setName ( characterLayer . name ) ;
if ( characterLayer . url !== undefined ) {
characterLayerMessage . setUrl ( characterLayer . url ) ;
}
joinRoomMessage . addCharacterlayer ( characterLayerMessage ) ;
}
console . log ( 'Calling joinRoom' )
const apiClient = await apiClientRepository . getClient ( client . roomId ) ;
const streamToPusher = apiClient . joinRoom ( ) ;
2021-02-09 23:21:11 +01:00
clientEventsEmitter . emitClientJoin ( client . userUuid , client . roomId ) ;
2020-11-13 18:00:22 +01:00
client . backConnection = streamToPusher ;
streamToPusher . on ( 'data' , ( message : ServerToClientMessage ) = > {
if ( message . hasRoomjoinedmessage ( ) ) {
2020-12-02 17:51:46 +01:00
client . userId = ( message . getRoomjoinedmessage ( ) as RoomJoinedMessage ) . getCurrentuserid ( ) ;
// TODO: do we need this.sockets anymore?
this . sockets . set ( client . userId , client ) ;
2020-11-13 18:00:22 +01:00
// If this is the first message sent, send back the viewport.
this . handleViewport ( client , viewport ) ;
}
// Let's pass data over from the back to the client.
if ( ! client . disconnecting ) {
client . send ( message . serializeBinary ( ) . buffer , true ) ;
}
} ) . on ( 'end' , ( ) = > {
console . warn ( 'Connection lost to back server' ) ;
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
if ( ! client . disconnecting ) {
2020-12-03 16:39:44 +01:00
this . closeWebsocketConnection ( client , 1011 , 'Connection lost to back server' ) ;
2020-11-13 18:00:22 +01:00
}
console . log ( 'A user left' ) ;
} ) . on ( 'error' , ( err : Error ) = > {
console . error ( 'Error in connection to back server:' , err ) ;
if ( ! client . disconnecting ) {
2020-12-03 16:39:44 +01:00
this . closeWebsocketConnection ( client , 1011 , 'Error while connecting to back server' ) ;
2020-11-13 18:00:22 +01:00
}
} ) ;
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setJoinroommessage ( joinRoomMessage ) ;
streamToPusher . write ( pusherToBackMessage ) ;
} catch ( e ) {
console . error ( 'An error occurred on "join_room" event' ) ;
console . error ( e ) ;
}
}
2020-12-10 17:46:15 +01:00
private closeWebsocketConnection ( client : ExSocketInterface | ExAdminSocketInterface , code : number , reason : string ) {
2020-11-13 18:00:22 +01:00
client . disconnecting = true ;
//this.leaveRoom(client);
2020-12-03 16:39:44 +01:00
//client.close();
client . end ( code , reason ) ;
2020-11-13 18:00:22 +01:00
}
handleViewport ( client : ExSocketInterface , viewport : ViewportMessage.AsObject ) {
try {
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 ) ;
}
}
handleUserMovesMessage ( client : ExSocketInterface , userMovesMessage : UserMovesMessage ) {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setUsermovesmessage ( userMovesMessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
const viewport = userMovesMessage . getViewport ( ) ;
if ( viewport === undefined ) {
throw new Error ( 'Missing viewport in UserMovesMessage' ) ;
}
// Now, we need to listen to the correct viewport.
this . handleViewport ( client , viewport . toObject ( ) )
}
// Useless now, will be useful again if we allow editing details in game
handleSetPlayerDetails ( client : ExSocketInterface , playerDetailsMessage : SetPlayerDetailsMessage ) {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setSetplayerdetailsmessage ( playerDetailsMessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
}
handleSilentMessage ( client : ExSocketInterface , silentMessage : SilentMessage ) {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setSilentmessage ( silentMessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
}
handleItemEvent ( client : ExSocketInterface , itemEventMessage : ItemEventMessage ) {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setItemeventmessage ( itemEventMessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
}
async handleReportMessage ( client : ExSocketInterface , reportPlayerMessage : ReportPlayerMessage ) {
try {
const reportedSocket = this . sockets . get ( reportPlayerMessage . getReporteduserid ( ) ) ;
if ( ! reportedSocket ) {
throw 'reported socket user not found' ;
}
//TODO report user on admin application
2021-01-29 21:09:10 +01:00
await adminApi . reportPlayer ( reportedSocket . userUuid , reportPlayerMessage . getReportcomment ( ) , client . userUuid , client . roomId . split ( '/' ) [ 2 ] )
2020-11-13 18:00:22 +01:00
} catch ( e ) {
console . error ( 'An error occurred on "handleReportMessage"' ) ;
console . error ( e ) ;
}
}
emitVideo ( socket : ExSocketInterface , data : WebRtcSignalToServerMessage ) : void {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setWebrtcsignaltoservermessage ( data ) ;
socket . backConnection . write ( pusherToBackMessage ) ;
}
emitScreenSharing ( socket : ExSocketInterface , data : WebRtcSignalToServerMessage ) : void {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setWebrtcscreensharingsignaltoservermessage ( data ) ;
socket . backConnection . write ( pusherToBackMessage ) ;
}
private searchClientByIdOrFail ( userId : number ) : ExSocketInterface {
const client : ExSocketInterface | undefined = this . sockets . get ( userId ) ;
if ( client === undefined ) {
throw new Error ( "Could not find user with id " + userId ) ;
}
return client ;
}
leaveRoom ( socket : ExSocketInterface ) {
// leave previous room and world
try {
if ( socket . roomId ) {
try {
//user leaves room
const room : PusherRoom | undefined = this . Worlds . get ( socket . roomId ) ;
if ( room ) {
debug ( 'Leaving room %s.' , socket . roomId ) ;
room . leave ( socket ) ;
if ( room . isEmpty ( ) ) {
this . Worlds . delete ( socket . roomId ) ;
debug ( 'Room %s is empty. Deleting.' , socket . roomId ) ;
}
} else {
console . error ( 'Could not find the GameRoom the user is leaving!' ) ;
}
//user leave previous room
//Client.leave(Client.roomId);
} finally {
//delete Client.roomId;
this . sockets . delete ( socket . userId ) ;
clientEventsEmitter . emitClientLeave ( socket . userUuid , socket . roomId ) ;
console . log ( 'A user left (' , this . sockets . size , ' connected users)' ) ;
}
}
} finally {
if ( socket . backConnection ) {
socket . backConnection . end ( ) ;
}
}
}
async getOrCreateRoom ( roomId : string ) : Promise < PusherRoom > {
//check and create new world for a room
let world = this . Worlds . get ( roomId )
if ( world === undefined ) {
2021-03-05 18:25:27 +01:00
world = new PusherRoom ( roomId , this ) ;
2020-11-13 18:00:22 +01: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 )
}
this . Worlds . set ( roomId , world ) ;
}
return Promise . resolve ( world )
}
emitPlayGlobalMessage ( client : ExSocketInterface , playglobalmessage : PlayGlobalMessage ) {
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setPlayglobalmessage ( playglobalmessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
}
public getWorlds ( ) : Map < string , PusherRoom > {
return this . Worlds ;
}
2021-03-01 17:47:00 +01:00
2020-11-13 18:00:22 +01:00
searchClientByUuid ( uuid : string ) : ExSocketInterface | null {
for ( const socket of this . sockets . values ( ) ) {
if ( socket . userUuid === uuid ) {
return socket ;
}
}
return null ;
}
public handleQueryJitsiJwtMessage ( client : ExSocketInterface , queryJitsiJwtMessage : QueryJitsiJwtMessage ) {
2021-03-10 09:41:54 +01:00
try {
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.
2020-11-13 18:00:22 +01:00
2021-03-10 09:41:54 +01: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-11-13 18:00:22 +01:00
2021-03-10 09:41:54 +01:00
// Let's see if the current client has
const isAdmin = client . tags . includes ( tag ) ;
const jwt = Jwt . sign ( {
"aud" : "jitsi" ,
"iss" : JITSI_ISS ,
"sub" : JITSI_URL ,
"room" : room ,
"moderator" : isAdmin
} , SECRET_JITSI_KEY , {
expiresIn : '1d' ,
algorithm : "HS256" ,
header :
{
"alg" : "HS256" ,
"typ" : "JWT"
}
} ) ;
2020-11-13 18:00:22 +01:00
2021-03-10 09:41:54 +01:00
const sendJitsiJwtMessage = new SendJitsiJwtMessage ( ) ;
sendJitsiJwtMessage . setJitsiroom ( room ) ;
sendJitsiJwtMessage . setJwt ( jwt ) ;
2020-11-13 18:00:22 +01:00
2021-03-10 09:41:54 +01:00
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setSendjitsijwtmessage ( sendJitsiJwtMessage ) ;
2020-11-13 18:00:22 +01:00
2021-03-10 09:41:54 +01:00
client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
} catch ( e ) {
console . error ( 'An error occured while generating the Jitsi JWT token: ' , e ) ;
}
2020-11-13 18:00:22 +01:00
}
2021-03-11 01:25:36 +01:00
public async emitSendUserMessage ( userUuid : string , message : string , type : string , roomId : string ) {
2021-03-11 12:17:37 +01:00
/ * c o n s t c l i e n t = t h i s . s e a r c h C l i e n t B y U u i d ( u s e r U u i d ) ;
2021-03-11 01:25:36 +01:00
if ( client ) {
const adminMessage = new SendUserMessage ( ) ;
adminMessage . setMessage ( message ) ;
adminMessage . setType ( type ) ;
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setSendusermessage ( adminMessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
return ;
2021-03-11 12:17:37 +01:00
} * /
2020-12-11 12:23:50 +01:00
2021-03-11 01:25:36 +01:00
const backConnection = await apiClientRepository . getClient ( roomId ) ;
const backAdminMessage = new AdminMessage ( ) ;
backAdminMessage . setMessage ( message ) ;
backAdminMessage . setRoomid ( roomId ) ;
backAdminMessage . setRecipientuuid ( userUuid ) ;
backAdminMessage . setType ( type ) ;
backConnection . sendAdminMessage ( backAdminMessage , ( error ) = > {
2020-12-11 12:23:50 +01:00
if ( error !== null ) {
console . error ( 'Error while sending admin message' , error ) ;
}
2021-03-11 01:25:36 +01:00
} ) ;
2020-12-11 12:23:50 +01:00
}
2021-03-11 01:25:36 +01:00
public async emitBan ( userUuid : string , message : string , type : string , roomId : string ) {
2021-03-11 12:17:37 +01:00
/ * c o n s t c l i e n t = t h i s . s e a r c h C l i e n t B y U u i d ( u s e r U u i d ) ;
2021-03-11 01:25:36 +01:00
if ( client ) {
const banUserMessage = new BanUserMessage ( ) ;
banUserMessage . setMessage ( message ) ;
banUserMessage . setType ( type ) ;
const pusherToBackMessage = new PusherToBackMessage ( ) ;
pusherToBackMessage . setBanusermessage ( banUserMessage ) ;
client . backConnection . write ( pusherToBackMessage ) ;
return ;
2021-03-11 12:17:37 +01:00
} * /
2020-12-11 12:23:50 +01:00
2021-03-11 01:25:36 +01:00
const backConnection = await apiClientRepository . getClient ( roomId ) ;
const banMessage = new BanMessage ( ) ;
banMessage . setMessage ( message ) ;
banMessage . setRoomid ( roomId ) ;
banMessage . setRecipientuuid ( userUuid ) ;
banMessage . setType ( type ) ;
backConnection . ban ( banMessage , ( error ) = > {
2020-12-11 12:23:50 +01:00
if ( error !== null ) {
2021-01-17 03:07:46 +01:00
console . error ( 'Error while sending admin message' , error ) ;
2020-12-11 12:23:50 +01:00
}
2021-03-11 01:25:36 +01:00
} ) ;
2020-11-13 18:00:22 +01:00
}
/ * *
* Merges the characterLayers received from the front ( as an array of string ) with the custom textures from the back .
* /
static mergeCharacterLayersAndCustomTextures ( characterLayers : string [ ] , memberTextures : CharacterTexture [ ] ) : CharacterLayer [ ] {
const characterLayerObjs : CharacterLayer [ ] = [ ] ;
for ( const characterLayer of characterLayers ) {
if ( characterLayer . startsWith ( 'customCharacterTexture' ) ) {
const customCharacterLayerId : number = + characterLayer . substr ( 22 ) ;
for ( const memberTexture of memberTextures ) {
if ( memberTexture . id == customCharacterLayerId ) {
characterLayerObjs . push ( {
name : characterLayer ,
url : memberTexture.url
} )
break ;
}
}
} else {
characterLayerObjs . push ( {
name : characterLayer ,
url : undefined
} )
}
}
return characterLayerObjs ;
}
public onUserEnters ( user : UserDescriptor , listener : ExSocketInterface ) : void {
const subMessage = new SubMessage ( ) ;
subMessage . setUserjoinedmessage ( user . toUserJoinedMessage ( ) ) ;
emitInBatch ( listener , subMessage ) ;
}
public onUserMoves ( user : UserDescriptor , listener : ExSocketInterface ) : void {
const subMessage = new SubMessage ( ) ;
subMessage . setUsermovedmessage ( user . toUserMovedMessage ( ) ) ;
emitInBatch ( listener , subMessage ) ;
}
public onUserLeaves ( userId : number , listener : ExSocketInterface ) : void {
const userLeftMessage = new UserLeftMessage ( ) ;
userLeftMessage . setUserid ( userId ) ;
const subMessage = new SubMessage ( ) ;
subMessage . setUserleftmessage ( userLeftMessage ) ;
emitInBatch ( listener , subMessage ) ;
}
public onGroupEnters ( group : GroupDescriptor , listener : ExSocketInterface ) : void {
const subMessage = new SubMessage ( ) ;
subMessage . setGroupupdatemessage ( group . toGroupUpdateMessage ( ) ) ;
emitInBatch ( listener , subMessage ) ;
}
public onGroupMoves ( group : GroupDescriptor , listener : ExSocketInterface ) : void {
this . onGroupEnters ( group , listener ) ;
}
public onGroupLeaves ( groupId : number , listener : ExSocketInterface ) : void {
const groupDeleteMessage = new GroupDeleteMessage ( ) ;
groupDeleteMessage . setGroupid ( groupId ) ;
const subMessage = new SubMessage ( ) ;
subMessage . setGroupdeletemessage ( groupDeleteMessage ) ;
emitInBatch ( listener , subMessage ) ;
}
2021-03-11 16:14:34 +01:00
public emitWorldFullMessage ( client : WebSocket ) {
const errorMessage = new WorldFullMessage ( ) ;
const serverToClientMessage = new ServerToClientMessage ( ) ;
serverToClientMessage . setWorldfullmessage ( errorMessage ) ;
client . send ( serverToClientMessage . serializeBinary ( ) . buffer , true ) ;
}
2020-11-13 18:00:22 +01:00
}
export const socketManager = new SocketManager ( ) ;