1 import socketIO = require('socket.io');
2 import {Socket} from "socket.io";
3 import * as http from "http";
4 import {MessageUserPosition} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.."
5 import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
6 import Jwt, {JsonWebTokenError} from "jsonwebtoken";
7 import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
8 import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRooms";
9 import {ExtRoomsInterface} from "../Model/Websocket/ExtRoomsInterface";
10 import {World} from "../Model/World";
11 import {Group} from "_Model/Group";
12 import {UserInterface} from "_Model/UserInterface";
15 CONNECTION = "connection",
16 DISCONNECT = "disconnect",
17 ATTRIBUTE_USER_ID = "attribute-user-id", // Sent from server to client just after the connexion is established to give the client its unique id.
18 JOIN_ROOM = "join-room",
19 USER_POSITION = "user-position",
20 WEBRTC_SIGNAL = "webrtc-signal",
21 WEBRTC_OFFER = "webrtc-offer",
22 WEBRTC_START = "webrtc-start",
23 WEBRTC_DISCONNECT = "webrtc-disconect",
24 MESSAGE_ERROR = "message-error",
25 GROUP_CREATE_UPDATE = "group-create-update",
26 GROUP_DELETE = "group-delete",
29 export class IoSocketController {
31 Worlds: Map<string, World> = new Map<string, World>();
33 constructor(server: http.Server) {
34 this.Io = socketIO(server);
36 // Authentication with token. it will be decoded and stored in the socket.
37 // Completely commented for now, as we do not use the "/login" route at all.
38 /*this.Io.use((socket: Socket, next) => {
39 if (!socket.handshake.query || !socket.handshake.query.token) {
40 return next(new Error('Authentication error'));
42 if(this.searchClientByToken(socket.handshake.query.token)){
43 return next(new Error('Authentication error'));
45 Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
47 return next(new Error('Authentication error'));
49 (socket as ExSocketInterface).token = tokenDecoded;
55 this.shareUsersPosition();
58 private sendUpdateGroupEvent(group: Group): void {
59 // Let's get the room of the group. To do this, let's get anyone in the group and find its room.
60 // Note: this is suboptimal
61 let userId = group.getUsers()[0].id;
62 let client: ExSocketInterface|null = this.searchClientById(userId);
63 if (client === null) {
66 let roomId = client.roomId;
67 this.Io.in(roomId).emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
68 position: group.getPosition(),
69 groupId: group.getId()
73 private sendDeleteGroupEvent(uuid: string, lastUser: UserInterface): void {
74 // Let's get the room of the group. To do this, let's get anyone in the group and find its room.
75 // Note: this is suboptimal
76 let userId = lastUser.id;
77 let client: ExSocketInterface|null = this.searchClientById(userId);
78 if (client === null) {
79 console.warn('Could not find client ', userId, ' in group')
82 let roomId = client.roomId;
83 this.Io.in(roomId).emit(SockerIoEvent.GROUP_DELETE, uuid);
87 this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => {
88 /*join-rom event permit to join one room.
90 userId : user identification
91 roomId: room identification
92 position: position of user in map
93 x: user x position on map
94 y: user y position on map
96 socket.on(SockerIoEvent.JOIN_ROOM, (message: string) => {
98 let messageUserPosition = this.hydrateMessageReceive(message);
99 if (messageUserPosition instanceof Error) {
100 return socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: messageUserPosition.message})
103 let Client = (socket as ExSocketInterface);
105 if (Client.roomId === messageUserPosition.roomId) {
109 //leave previous room
110 this.leaveRoom(Client);
112 //join new previous room
113 this.joinRoom(Client, messageUserPosition);
115 // sending to all clients in room except sender
116 this.saveUserInformation(Client, messageUserPosition);
118 //add function to refresh position user in real time.
119 this.refreshUserPosition(Client);
121 socket.to(messageUserPosition.roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserPosition.toString());
123 console.error('An error occurred on "join_room" event');
128 socket.on(SockerIoEvent.USER_POSITION, (message: string) => {
130 let messageUserPosition = this.hydrateMessageReceive(message);
131 if (messageUserPosition instanceof Error) {
132 return socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: messageUserPosition.message});
135 let Client = (socket as ExSocketInterface);
137 // sending to all clients in room except sender
138 this.saveUserInformation(Client, messageUserPosition);
140 //refresh position of all user in all rooms in real time
141 this.refreshUserPosition(Client);
143 console.error('An error occurred on "user_position" event');
148 socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: any) => {
150 let client = this.searchClientById(data.receiverId);
152 console.error("client doesn't exist for ", data.receiverId);
155 return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data);
158 socket.on(SockerIoEvent.WEBRTC_OFFER, (data: any) => {
161 let client = this.searchClientById(data.receiverId);
163 console.error("client doesn't exist for ", data.receiverId);
166 client.emit(SockerIoEvent.WEBRTC_OFFER, data);
169 socket.on(SockerIoEvent.DISCONNECT, () => {
171 let Client = (socket as ExSocketInterface);
172 this.sendDisconnectedEvent(Client);
174 //refresh position of all user in all rooms in real time
175 this.refreshUserPosition(Client);
178 this.leaveRoom(Client);
181 socket.leave(Client.webRtcRoomId);
183 //delete all socket information
184 delete Client.webRtcRoomId;
185 delete Client.roomId;
187 delete Client.position;
189 console.error('An error occurred on "disconnect"');
194 // Let's send the user id to the user
195 socket.emit(SockerIoEvent.ATTRIBUTE_USER_ID, socket.id);
200 * TODO: each call to this method is suboptimal. It means that instead of passing an ID, we should pass a client object.
203 searchClientById(userId: string): ExSocketInterface | null {
204 let clients: Array<any> = Object.values(this.Io.sockets.sockets);
205 for (let i = 0; i < clients.length; i++) {
206 let client: ExSocketInterface = clients[i];
207 if (client.id !== userId) {
212 console.log("Could not find user with id ", userId);
213 throw new Error("Could not find user with id " + userId);
220 searchClientByToken(userId: string): ExSocketInterface | null {
221 let clients: Array<any> = Object.values(this.Io.sockets.sockets);
222 for (let i = 0; i < clients.length; i++) {
223 let client: ExSocketInterface = clients[i];
224 if (client.id !== userId) {
234 * @param Client: ExSocketInterface
236 sendDisconnectedEvent(Client: ExSocketInterface) {
237 Client.broadcast.emit(SockerIoEvent.WEBRTC_DISCONNECT, {
241 //disconnect webrtc room
242 if(!Client.webRtcRoomId){
245 Client.leave(Client.webRtcRoomId);
246 delete Client.webRtcRoomId;
253 leaveRoom(Client : ExSocketInterface){
254 //lease previous room and world
256 //user leave previous world
257 let world : World|undefined = this.Worlds.get(Client.roomId);
260 //this.Worlds.set(Client.roomId, world);
262 //user leave previous room
263 Client.leave(Client.roomId);
264 delete Client.roomId;
270 * @param messageUserPosition
272 joinRoom(Client : ExSocketInterface, messageUserPosition: MessageUserPosition){
274 Client.join(messageUserPosition.roomId);
276 //check and create new world for a room
277 if(!this.Worlds.get(messageUserPosition.roomId)){
278 let world = new World((user1: string, group: Group) => {
279 this.connectedUser(user1, group);
280 }, (user1: string, group: Group) => {
281 this.disConnectedUser(user1, group);
282 }, MINIMUM_DISTANCE, GROUP_RADIUS, (group: Group) => {
283 this.sendUpdateGroupEvent(group);
284 }, (groupUuid: string, lastUser: UserInterface) => {
285 this.sendDeleteGroupEvent(groupUuid, lastUser);
287 this.Worlds.set(messageUserPosition.roomId, world);
290 let world : World|undefined = this.Worlds.get(messageUserPosition.roomId);
294 // Dispatch groups position to newly connected user
295 world.getGroups().forEach((group: Group) => {
296 Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
297 position: group.getPosition(),
298 groupId: group.getId()
302 world.join(Client, messageUserPosition);
303 this.Worlds.set(messageUserPosition.roomId, world);
314 joinWebRtcRoom(socket: ExSocketInterface, roomId: string) {
315 if (socket.webRtcRoomId === roomId) {
319 socket.webRtcRoomId = roomId;
320 //if two persons in room share
321 if (this.Io.sockets.adapter.rooms[roomId].length < 2 /*|| this.Io.sockets.adapter.rooms[roomId].length >= 4*/) {
324 let clients: Array<ExSocketInterface> = (Object.values(this.Io.sockets.sockets) as Array<ExSocketInterface>)
325 .filter((client: ExSocketInterface) => client.webRtcRoomId && client.webRtcRoomId === roomId);
326 //send start at one client to initialise offer webrtc
327 //send all users in room to create PeerConnection in front
328 clients.forEach((client: ExSocketInterface, index: number) => {
330 let clientsId = clients.reduce((tabs: Array<any>, clientId: ExSocketInterface, indexClientId: number) => {
331 if (!clientId.id || clientId.id === client.id) {
337 initiator: index <= indexClientId
342 client.emit(SockerIoEvent.WEBRTC_START, {clients: clientsId, roomId: roomId});
346 //permit to save user position in socket
347 saveUserInformation(socket: ExSocketInterface, message: MessageUserPosition) {
348 socket.position = message.position;
349 socket.roomId = message.roomId;
350 //socket.userId = message.userId;
351 socket.name = message.name;
352 socket.character = message.character;
355 refreshUserPosition(Client : ExSocketInterface) {
356 //refresh position of all user in all rooms in real time
357 let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface);
358 if (!rooms.refreshUserPosition) {
359 rooms.refreshUserPosition = RefreshUserPositionFunction;
361 rooms.refreshUserPosition(rooms, this.Io);
363 // update position in the world
366 roomId: Client.roomId,
367 position: Client.position,
369 character: Client.character,
371 let messageUserPosition = new MessageUserPosition(data);
372 let world = this.Worlds.get(messageUserPosition.roomId);
376 world.updatePosition(Client, messageUserPosition);
377 this.Worlds.set(messageUserPosition.roomId, world);
380 //Hydrate and manage error
381 hydrateMessageReceive(message: string): MessageUserPosition | Error {
383 return new MessageUserPosition(message);
386 return new Error(err);
390 /** permit to share user position
391 ** users position will send in event 'user-position'
392 ** The data sent is an array with information for each user :
406 seTimeOutInProgress: any = null;
408 shareUsersPosition() {
409 if (this.seTimeOutInProgress) {
410 clearTimeout(this.seTimeOutInProgress);
412 //send for each room, all data of position user
413 let arrayMap = (this.Io.sockets.adapter.rooms as ExtRooms).userPositionMapByRoom;
415 this.seTimeOutInProgress = setTimeout(() => {
416 this.shareUsersPosition();
420 arrayMap.forEach((value: any) => {
421 let roomId = value[0];
422 this.Io.in(roomId).emit(SockerIoEvent.USER_POSITION, value);
424 this.seTimeOutInProgress = setTimeout(() => {
425 this.shareUsersPosition();
430 connectedUser(userId: string, group: Group) {
431 let Client = this.searchClientById(userId);
435 this.joinWebRtcRoom(Client, group.getId());
439 disConnectedUser(userId: string, group: Group) {
440 let Client = this.searchClientById(userId);
444 this.sendDisconnectedEvent(Client)