From a19edd4dc1f13232a2ef8ab4178818415a1cff8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Dec 2020 16:39:44 +0100 Subject: [PATCH] Fixing reconnection to server on back failure --- back/src/Model/GameRoom.ts | 1 - front/src/Connexion/ConnectionManager.ts | 25 ++++++++----- front/src/Connexion/ConnexionModels.ts | 9 ++++- front/src/Connexion/RoomConnection.ts | 43 ++++++++++++++++++---- front/src/Phaser/Game/GameScene.ts | 47 ++++++++++++------------ pusher/src/Services/SocketManager.ts | 9 +++-- 6 files changed, 87 insertions(+), 47 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 5572c663..4fcf6ec9 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -96,7 +96,6 @@ export class GameRoom { } const position = ProtobufUtils.toPointInterface(positionMessage); - const user = new User(this.nextUserId, joinRoomMessage.getUseruuid(), position, false, this.positionNotifier, socket, joinRoomMessage.getTagList(), joinRoomMessage.getName(), ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList())); this.nextUserId++; this.users.set(user.id, user); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 06a2eedf..4b71777a 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -1,7 +1,7 @@ import Axios from "axios"; import {API_URL} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "./RoomConnection"; -import {PositionInterface, ViewportInterface} from "./ConnexionModels"; +import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels"; import {GameConnexionTypes, urlManager} from "../Url/UrlManager"; import {localUserStore} from "./LocalUserStore"; import {LocalUser} from "./LocalUser"; @@ -88,24 +88,29 @@ class ConnectionManager { this.localUser = new LocalUser('', 'test', []); } - public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise { - return new Promise((resolve, reject) => { + public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise { + return new Promise((resolve, reject) => { const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport); connection.onConnectError((error: object) => { console.log('An error occurred while connecting to socket server. Retrying'); reject(error); }); - // FIXME: onConnect should be triggered by the first JoinRoomEvent (instead of the connection) - connection.onConnect(() => { - console.warn('CONNECT RECEIVED'); - resolve(connection); - }) + connection.onConnectingError((event: CloseEvent) => { + console.log('An error occurred while connecting to socket server. Retrying'); + reject(new Error('An error occurred while connecting to socket server. Retrying. Code: '+event.code+', Reason: '+event.reason)); + }); + + connection.onConnect((connect: OnConnectInterface) => { + resolve(connect); + }); + }).catch((err) => { // Let's retry in 4-6 seconds - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { setTimeout(() => { - //todo: allow a way to break recurrsion? + //todo: allow a way to break recursion? + //todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely. this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection)); }, 4000 + Math.floor(Math.random() * 2000) ); }); diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 145f8fca..6b17fde8 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -2,13 +2,14 @@ import {PlayerAnimationNames} from "../Phaser/Player/Animation"; import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import {SignalData} from "simple-peer"; import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character"; +import {RoomConnection} from "./RoomConnection"; export enum EventMessage{ CONNECT = "connect", WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", - START_ROOM = "start-room", // From server to client: list of all room users/groups/items + //START_ROOM = "start-room", // From server to client: list of all room users/groups/items JOIN_ROOM = "join-room", // bi-directional USER_POSITION = "user-position", // From client to server USER_MOVED = "user-moved", // From server to client @@ -21,6 +22,7 @@ export enum EventMessage{ ITEM_EVENT = 'item-event', CONNECT_ERROR = "connect_error", + CONNECTING_ERROR = "connecting_error", SET_SILENT = "set_silent", // Set or unset the silent mode for this user. SET_VIEWPORT = "set-viewport", BATCH = "batch", @@ -132,3 +134,8 @@ export interface PlayGlobalMessageInterface { type: string message: string } + +export interface OnConnectInterface { + connection: RoomConnection, + room: RoomJoinedMessageInterface +} diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 71eabcb5..bc6b6fd7 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -36,7 +36,7 @@ import {ProtobufClientUtils} from "../Network/ProtobufClientUtils"; import { EventMessage, GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, - MessageUserJoined, PlayGlobalMessageInterface, PositionInterface, + MessageUserJoined, OnConnectInterface, PlayGlobalMessageInterface, PositionInterface, RoomJoinedMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface, WebRtcSignalReceivedMessageInterface, @@ -86,14 +86,26 @@ export class RoomConnection implements RoomConnection { this.socket.binaryType = 'arraybuffer'; + let interval: ReturnType|undefined = undefined; + this.socket.onopen = (ev) => { //we manually ping every 20s to not be logged out by the server, even when the game is in background. const pingMessage = new PingMessage(); - setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay); + interval = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay); }; + this.socket.addEventListener('close', (event) => { + if (interval) { + clearInterval(interval); + } + + // If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry. + if (this.userId === null) { + this.dispatch(EventMessage.CONNECTING_ERROR, event); + } + }); + this.socket.onmessage = (messageEvent) => { - console.warn('message received'); const arrayBuffer: ArrayBuffer = messageEvent.data; const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer)); @@ -138,13 +150,22 @@ export class RoomConnection implements RoomConnection { this.userId = roomJoinedMessage.getCurrentuserid(); this.tags = roomJoinedMessage.getTagList(); - this.dispatch(EventMessage.CONNECT, this); + //console.log('Dispatching CONNECT') + this.dispatch(EventMessage.CONNECT, { + connection: this, + room: { + //users, + //groups, + items + } as RoomJoinedMessageInterface + }); + /*console.log('Dispatching START_ROOM') this.dispatch(EventMessage.START_ROOM, { //users, //groups, items - }); + });*/ } else if (message.hasErrormessage()) { console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage()); } else if (message.hasWebrtcsignaltoclientmessage()) { @@ -354,6 +375,12 @@ export class RoomConnection implements RoomConnection { }); } + public onConnectingError(callback: (event: CloseEvent) => void): void { + this.onMessage(EventMessage.CONNECTING_ERROR, (event: CloseEvent) => { + callback(event); + }); + } + public onConnectError(callback: (error: Event) => void): void { this.socket.addEventListener('error', callback) } @@ -361,7 +388,7 @@ export class RoomConnection implements RoomConnection { /*public onConnect(callback: (e: Event) => void): void { this.socket.addEventListener('open', callback) }*/ - public onConnect(callback: (roomConnection: RoomConnection) => void): void { + public onConnect(callback: (roomConnection: OnConnectInterface) => void): void { //this.socket.addEventListener('open', callback) this.onMessage(EventMessage.CONNECT, callback); } @@ -369,9 +396,9 @@ export class RoomConnection implements RoomConnection { /** * Triggered when we receive all the details of a room (users, groups, ...) */ - public onStartRoom(callback: (event: RoomJoinedMessageInterface) => void): void { + /*public onStartRoom(callback: (event: RoomJoinedMessageInterface) => void): void { this.onMessage(EventMessage.START_ROOM, callback); - } + }*/ public sendWebrtcSignal(signal: unknown, receiverId: number) { const webRtcSignal = new WebRtcSignalToServerMessage(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a5b8a045..6d0838ea 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -3,7 +3,7 @@ import { GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserMovedInterface, - MessageUserPositionInterface, + MessageUserPositionInterface, OnConnectInterface, PointInterface, PositionInterface, RoomJoinedMessageInterface @@ -521,23 +521,14 @@ export class GameScene extends ResizableScene implements CenterListener { top: camera.scrollY, right: camera.scrollX + camera.width, bottom: camera.scrollY + camera.height, - }).then((connection: RoomConnection) => { - this.connection = connection; + }).then((onConnect: OnConnectInterface) => { + this.connection = onConnect.connection; //this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected()) - connection.onStartRoom((roomJoinedMessage: RoomJoinedMessageInterface) => { - //this.initUsersPosition(roomJoinedMessage.users); - this.connectionAnswerPromiseResolve(roomJoinedMessage); - // Analyze tags to find if we are admin. If yes, show console. - if (this.connection.hasTag('admin')) { - this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager); - } + /*this.connection.onStartRoom((roomJoinedMessage: RoomJoinedMessageInterface) => { - this.scene.wake(); - this.scene.sleep(ReconnectingSceneName); - }); - - connection.onUserJoins((message: MessageUserJoined) => { + });*/ + this.connection.onUserJoins((message: MessageUserJoined) => { const userMessage: AddPlayerInterface = { userId: message.userId, characterLayers: message.characterLayers, @@ -547,7 +538,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.addPlayer(userMessage); }); - connection.onUserMoved((message: UserMovedMessage) => { + this.connection.onUserMoved((message: UserMovedMessage) => { const position = message.getPosition(); if (position === undefined) { throw new Error('Position missing from UserMovedMessage'); @@ -562,15 +553,15 @@ export class GameScene extends ResizableScene implements CenterListener { this.updatePlayerPosition(messageUserMoved); }); - connection.onUserLeft((userId: number) => { + this.connection.onUserLeft((userId: number) => { this.removePlayer(userId); }); - connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { + this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { this.shareGroupPosition(groupPositionMessage); }) - connection.onGroupDeleted((groupId: number) => { + this.connection.onGroupDeleted((groupId: number) => { try { this.deleteGroup(groupId); } catch (e) { @@ -578,7 +569,7 @@ export class GameScene extends ResizableScene implements CenterListener { } }) - connection.onServerDisconnected(() => { + this.connection.onServerDisconnected(() => { console.log('Player disconnected from server. Reloading scene.'); this.simplePeer.closeAllConnections(); @@ -599,7 +590,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.scene.remove(this.scene.key); }) - connection.onActionableEvent((message => { + this.connection.onActionableEvent((message => { const item = this.actionableItems.get(message.itemId); if (item === undefined) { console.warn('Received an event about object "' + message.itemId + '" but cannot find this item on the map.'); @@ -611,7 +602,7 @@ export class GameScene extends ResizableScene implements CenterListener { /** * Triggered when we receive the JWT token to connect to Jitsi */ - connection.onStartJitsiRoom((jwt, room) => { + this.connection.onStartJitsiRoom((jwt, room) => { this.startJitsi(room, jwt); }); @@ -641,7 +632,17 @@ export class GameScene extends ResizableScene implements CenterListener { this.gameMap.setPosition(event.x, event.y); }) - return connection; + //this.initUsersPosition(roomJoinedMessage.users); + this.connectionAnswerPromiseResolve(onConnect.room); + // Analyze tags to find if we are admin. If yes, show console. + if (this.connection.hasTag('admin')) { + this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager); + } + console.log('wakingup'); + this.scene.wake(); + this.scene.sleep(ReconnectingSceneName); + + return this.connection; }); } diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index c8f7d08e..b496622a 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -136,13 +136,13 @@ export class SocketManager implements ZoneEventListener { 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) { - this.closeWebsocketConnection(client); + 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); + this.closeWebsocketConnection(client, 1011, 'Error while connecting to back server'); } }); @@ -208,10 +208,11 @@ export class SocketManager implements ZoneEventListener { } } - closeWebsocketConnection(client: ExSocketInterface) { + private closeWebsocketConnection(client: ExSocketInterface, code: number, reason: string) { client.disconnecting = true; //this.leaveRoom(client); - client.close(); + //client.close(); + client.end(code, reason); } handleViewport(client: ExSocketInterface, viewport: ViewportMessage.AsObject) {