From af4611ed292093e01e9a118226eefd175ff36c78 Mon Sep 17 00:00:00 2001 From: arp Date: Fri, 25 Sep 2020 18:29:22 +0200 Subject: [PATCH 1/2] rewrote the login workflow --- back/src/Controller/AdminController.ts | 36 ---- back/src/Controller/AuthenticateController.ts | 65 +++++-- benchmark/index.ts | 4 +- front/src/Connexion/ConnectionManager.ts | 53 ++++++ front/src/Connexion/ConnexionModels.ts | 117 ++++++++++++ .../RoomConnection.ts} | 178 +++--------------- front/src/Network/ProtobufClientUtils.ts | 2 +- front/src/Phaser/Entity/RemotePlayer.ts | 2 +- front/src/Phaser/Game/AddPlayerInterface.ts | 2 +- front/src/Phaser/Game/GameManager.ts | 27 ++- front/src/Phaser/Game/GameScene.ts | 15 +- front/src/Phaser/Game/PlayerMovement.ts | 2 +- front/src/Phaser/Login/EnableCameraScene.ts | 7 +- .../src/Phaser/Login/SelectCharacterScene.ts | 2 - front/src/Phaser/Player/Player.ts | 6 +- front/src/WebRtc/ScreenSharingPeer.ts | 4 +- front/src/WebRtc/SimplePeer.ts | 8 +- front/src/WebRtc/VideoPeer.ts | 4 +- front/src/index.ts | 5 +- front/src/register.ts | 29 --- 20 files changed, 290 insertions(+), 278 deletions(-) delete mode 100644 back/src/Controller/AdminController.ts create mode 100644 front/src/Connexion/ConnectionManager.ts create mode 100644 front/src/Connexion/ConnexionModels.ts rename front/src/{Connection.ts => Connexion/RoomConnection.ts} (69%) delete mode 100644 front/src/register.ts diff --git a/back/src/Controller/AdminController.ts b/back/src/Controller/AdminController.ts deleted file mode 100644 index c4905a8a..00000000 --- a/back/src/Controller/AdminController.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Application, Request, Response} from "express"; -import {OK} from "http-status-codes"; -import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; -import Axios from "axios"; - -export class AdminController { - App : Application; - - constructor(App : Application) { - this.App = App; - this.getLoginUrlByToken(); - } - - - getLoginUrlByToken(){ - this.App.get("/register/:token", async (req: Request, res: Response) => { - if (!ADMIN_API_URL) { - return res.status(500).send('No admin backoffice set!'); - } - const token:string = req.params.token; - - let response = null - try { - response = await Axios.get(ADMIN_API_URL+'/api/login-url/'+token, { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }) - } catch (e) { - console.log(e.message) - return res.status(e.status || 500).send('An error happened'); - } - - const organizationSlug = response.data.organizationSlug; - const worldSlug = response.data.worldSlug; - const roomSlug = response.data.roomSlug; - return res.status(OK).send({organizationSlug, worldSlug, roomSlug}); - }); - } -} diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index 83880f45..26cc6ae5 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -1,8 +1,9 @@ import {Application, Request, Response} from "express"; import Jwt from "jsonwebtoken"; -import {BAD_REQUEST, OK} from "http-status-codes"; -import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." +import {OK} from "http-status-codes"; +import {ADMIN_API_TOKEN, ADMIN_API_URL, SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import { uuid } from 'uuidv4'; +import Axios from "axios"; export interface TokenInterface { name: string, @@ -20,21 +21,53 @@ export class AuthenticateController { //permit to login on application. Return token to connect on Websocket IO. login(){ // For now, let's completely forget the /login route. - this.App.post("/login", (req: Request, res: Response) => { - const param = req.body; - /*if(!param.name){ - return res.status(BAD_REQUEST).send({ - message: "email parameter is empty" + this.App.post("/login", async (req: Request, res: Response) => { + //todo: what to do if the organizationMemberToken is already used? + const organizationMemberToken:string|null = req.body.organizationMemberToken; + + try { + let userUuid; + let mapUrlStart; + let newUrl = null; + + if (organizationMemberToken) { + if (!ADMIN_API_URL) { + return res.status(401).send('No admin backoffice set!'); + } + //todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case. + const response = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken, + { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } + ); + + userUuid = response.data.userUuid; + mapUrlStart = response.data.mapUrlStart; + newUrl = this.getNewUrlOnAdminAuth(response.data) + } else { + userUuid = uuid(); + mapUrlStart= URL_ROOM_STARTED; + newUrl = null; + } + + const authToken = Jwt.sign({userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'}); + return res.status(OK).send({ + authToken, + userUuid, + mapUrlStart, + newUrl, }); - }*/ - //TODO check user email for The Coding Machine game - const userUuid = uuid(); - const token = Jwt.sign({name: param.name, userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'}); - return res.status(OK).send({ - token: token, - mapUrlStart: URL_ROOM_STARTED, - userId: userUuid, - }); + + } catch (e) { + console.log(e.message) + return res.status(e.status || 500).send('An error happened'); + } + }); } + + getNewUrlOnAdminAuth(data:any): string { + const organizationSlug = data.organizationSlug; + const worldSlug = data.worldSlug; + const roomSlug = data.roomSlug; + return '/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug; + } } diff --git a/benchmark/index.ts b/benchmark/index.ts index 736a7bdc..921f4cbb 100644 --- a/benchmark/index.ts +++ b/benchmark/index.ts @@ -1,11 +1,11 @@ -import {Connection} from "../front/src/Connection"; +import {RoomConnection} from "../front/src/Connexion/Connection"; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function startOneUser(): Promise { - const connection = await Connection.createConnection('foo', ['male3']); + const connection = await RoomConnection.createConnection('foo', ['male3']); await connection.joinARoom('global__maps.workadventure.localhost/Floor0/floor0', 783, 170, 'down', false, { top: 0, diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts new file mode 100644 index 00000000..cb70eef3 --- /dev/null +++ b/front/src/Connexion/ConnectionManager.ts @@ -0,0 +1,53 @@ +import Axios from "axios"; +import {API_URL} from "../Enum/EnvironmentVariable"; +import {RoomConnection} from "./RoomConnection"; + +class ConnectionManager { + private mapUrlStart: string|null = null; + + private authToken:string|null = null; + private userUuid: string|null = null; + private userName:string|null = null; + + public async init(): Promise { + const match = /\/register\/(.+)/.exec(window.location.toString()); + const organizationMemberToken = match ? match[1] : null; + const res = await Axios.post(`${API_URL}/login`, {organizationMemberToken}); + this.authToken = res.data.authToken; + this.userUuid = res.data.userUuid; + this.mapUrlStart = res.data.mapUrlStart; + const newUrl = res.data.newUrl; + + if (newUrl) { + history.pushState({}, '', newUrl); + } + } + + public async setUserName(name:string): Promise { + //todo + } + + public connectToRoomSocket(): Promise { + return Axios.post(`${API_URL}/connectToSocket`, {authToken: this.authToken}).then((res) => { + return new Promise((resolve, reject) => { + const connection = new RoomConnection(res.data.roomToken); + connection.onConnectError((error: object) => { + console.log('An error occurred while connecting to socket server. Retrying'); + reject(error); + }); + resolve(connection); + }); + }) + .catch((err) => { + // Let's retry in 4-6 seconds + return new Promise((resolve, reject) => { + setTimeout(() => { + //todo: allow a way to break recurrsion? + this.connectToRoomSocket().then((connection) => resolve(connection)); + }, 4000 + Math.floor(Math.random() * 2000) ); + }); + }); + } +} + +export const connectionManager = new ConnectionManager(); \ No newline at end of file diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts new file mode 100644 index 00000000..794b9169 --- /dev/null +++ b/front/src/Connexion/ConnexionModels.ts @@ -0,0 +1,117 @@ +import {PlayerAnimationNames} from "../Phaser/Player/Animation"; +import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; +import {SignalData} from "simple-peer"; + +export enum EventMessage{ + WEBRTC_SIGNAL = "webrtc-signal", + WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", + WEBRTC_START = "webrtc-start", + JOIN_ROOM = "join-room", // bi-directional + USER_POSITION = "user-position", // From client to server + USER_MOVED = "user-moved", // From server to client + USER_LEFT = "user-left", // From server to client + MESSAGE_ERROR = "message-error", + WEBRTC_DISCONNECT = "webrtc-disconect", + GROUP_CREATE_UPDATE = "group-create-update", + GROUP_DELETE = "group-delete", + SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. + ITEM_EVENT = 'item-event', + + CONNECT_ERROR = "connect_error", + SET_SILENT = "set_silent", // Set or unset the silent mode for this user. + SET_VIEWPORT = "set-viewport", + BATCH = "batch", +} + +export interface PointInterface { + x: number; + y: number; + direction : string; + moving: boolean; +} + +export class Point implements PointInterface{ + constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) { + if(x === null || y === null){ + throw Error("position x and y cannot be null"); + } + } +} + +export interface MessageUserPositionInterface { + userId: number; + name: string; + characterLayers: string[]; + position: PointInterface; +} + +export interface MessageUserMovedInterface { + userId: number; + position: PointInterface; +} + +export interface MessageUserJoined { + userId: number; + name: string; + characterLayers: string[]; + position: PointInterface +} + +export interface PositionInterface { + x: number, + y: number +} + +export interface GroupCreatedUpdatedMessageInterface { + position: PositionInterface, + groupId: number +} + +export interface WebRtcStartMessageInterface { + roomId: string, + clients: UserSimplePeerInterface[] +} + +export interface WebRtcDisconnectMessageInterface { + userId: number +} + +export interface WebRtcSignalSentMessageInterface { + receiverId: number, + signal: SignalData +} + +export interface WebRtcSignalReceivedMessageInterface { + userId: number, + signal: SignalData +} + +export interface StartMapInterface { + mapUrlStart: string, + startInstance: string +} + +export interface ViewportInterface { + left: number, + top: number, + right: number, + bottom: number, +} + +export interface BatchedMessageInterface { + event: string, + payload: unknown +} + +export interface ItemEventMessageInterface { + itemId: number, + event: string, + state: unknown, + parameters: unknown +} + +export interface RoomJoinedMessageInterface { + users: MessageUserPositionInterface[], + groups: GroupCreatedUpdatedMessageInterface[], + items: { [itemId: number] : unknown } +} \ No newline at end of file diff --git a/front/src/Connection.ts b/front/src/Connexion/RoomConnection.ts similarity index 69% rename from front/src/Connection.ts rename to front/src/Connexion/RoomConnection.ts index 61b0c4e7..99ef7b4e 100644 --- a/front/src/Connection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -1,142 +1,34 @@ -import Axios from "axios"; -import {API_URL} from "./Enum/EnvironmentVariable"; -import {MessageUI} from "./Logger/MessageUI"; +import {API_URL} from "../Enum/EnvironmentVariable"; import { BatchMessage, GroupDeleteMessage, GroupUpdateMessage, ItemEventMessage, PositionMessage, SetPlayerDetailsMessage, UserJoinedMessage, UserLeftMessage, UserMovedMessage, UserMovesMessage, ViewportMessage -} from "./Messages/generated/messages_pb" +} from "../Messages/generated/messages_pb" const SocketIo = require('socket.io-client'); import Socket = SocketIOClient.Socket; -import {PlayerAnimationNames} from "./Phaser/Player/Animation"; -import {UserSimplePeerInterface} from "./WebRtc/SimplePeer"; -import {SignalData} from "simple-peer"; import Direction = PositionMessage.Direction; -import {ProtobufClientUtils} from "./Network/ProtobufClientUtils"; +import {ProtobufClientUtils} from "../Network/ProtobufClientUtils"; +import { + EventMessage, + GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, + MessageUserJoined, + RoomJoinedMessageInterface, + ViewportInterface, WebRtcDisconnectMessageInterface, + WebRtcSignalReceivedMessageInterface, + WebRtcSignalSentMessageInterface, + WebRtcStartMessageInterface +} from "./ConnexionModels"; -enum EventMessage{ - WEBRTC_SIGNAL = "webrtc-signal", - WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", - WEBRTC_START = "webrtc-start", - JOIN_ROOM = "join-room", // bi-directional - USER_POSITION = "user-position", // From client to server - USER_MOVED = "user-moved", // From server to client - USER_LEFT = "user-left", // From server to client - MESSAGE_ERROR = "message-error", - WEBRTC_DISCONNECT = "webrtc-disconect", - GROUP_CREATE_UPDATE = "group-create-update", - GROUP_DELETE = "group-delete", - SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. - ITEM_EVENT = 'item-event', - CONNECT_ERROR = "connect_error", - SET_SILENT = "set_silent", // Set or unset the silent mode for this user. - SET_VIEWPORT = "set-viewport", - BATCH = "batch", -} - -export interface PointInterface { - x: number; - y: number; - direction : string; - moving: boolean; -} - -export class Point implements PointInterface{ - constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) { - if(x === null || y === null){ - throw Error("position x and y cannot be null"); - } - } -} - -export interface MessageUserPositionInterface { - userId: number; - name: string; - characterLayers: string[]; - position: PointInterface; -} - -export interface MessageUserMovedInterface { - userId: number; - position: PointInterface; -} - -export interface MessageUserJoined { - userId: number; - name: string; - characterLayers: string[]; - position: PointInterface -} - -export interface PositionInterface { - x: number, - y: number -} - -export interface GroupCreatedUpdatedMessageInterface { - position: PositionInterface, - groupId: number -} - -export interface WebRtcStartMessageInterface { - roomId: string, - clients: UserSimplePeerInterface[] -} - -export interface WebRtcDisconnectMessageInterface { - userId: number -} - -export interface WebRtcSignalSentMessageInterface { - receiverId: number, - signal: SignalData -} - -export interface WebRtcSignalReceivedMessageInterface { - userId: number, - signal: SignalData -} - -export interface StartMapInterface { - mapUrlStart: string, - startInstance: string -} - -export interface ViewportInterface { - left: number, - top: number, - right: number, - bottom: number, -} - -export interface BatchedMessageInterface { - event: string, - payload: unknown -} - -export interface ItemEventMessageInterface { - itemId: number, - event: string, - state: unknown, - parameters: unknown -} - -export interface RoomJoinedMessageInterface { - users: MessageUserPositionInterface[], - groups: GroupCreatedUpdatedMessageInterface[], - items: { [itemId: number] : unknown } -} - -export class Connection implements Connection { +export class RoomConnection implements RoomConnection { private readonly socket: Socket; private userId: number|null = null; private batchCallbacks: Map = new Map(); - private constructor(token: string) { + public constructor(token: string) { this.socket = SocketIo(`${API_URL}`, { query: { @@ -190,38 +82,14 @@ export class Connection implements Connection { } }) } - - public static createConnection(name: string, characterLayersSelected: string[]): Promise { - return Axios.post(`${API_URL}/login`, {name: name}) - .then((res) => { - - return new Promise((resolve, reject) => { - const connection = new Connection(res.data.token); - - connection.onConnectError((error: object) => { - console.log('An error occurred while connecting to socket server. Retrying'); - reject(error); - }); - - const message = new SetPlayerDetailsMessage(); - message.setName(name); - message.setCharacterlayersList(characterLayersSelected); - connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: number) => { - connection.userId = id; - }); - - resolve(connection); - }); - }) - .catch((err) => { - // Let's retry in 4-6 seconds - return new Promise((resolve, reject) => { - setTimeout(() => { - Connection.createConnection(name, characterLayersSelected).then((connection) => resolve(connection)) - .catch((error) => reject(error)); - }, 4000 + Math.floor(Math.random() * 2000) ); - }); - }); + + public emitPlayerDetailsMessage(characterLayersSelected: string[]) { + const message = new SetPlayerDetailsMessage(); + message.setName(name); + message.setCharacterlayersList(characterLayersSelected); + this.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: number) => { + this.userId = id; + }); } public closeConnection(): void { diff --git a/front/src/Network/ProtobufClientUtils.ts b/front/src/Network/ProtobufClientUtils.ts index 1eb5b923..6a402e97 100644 --- a/front/src/Network/ProtobufClientUtils.ts +++ b/front/src/Network/ProtobufClientUtils.ts @@ -1,6 +1,6 @@ import {PositionMessage} from "../Messages/generated/messages_pb"; -import {PointInterface} from "../Connection"; import Direction = PositionMessage.Direction; +import {PointInterface} from "../Connexion/ConnexionModels"; export class ProtobufClientUtils { diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 00a3e4c4..ba0a74d2 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -1,5 +1,5 @@ import {GameScene} from "../Game/GameScene"; -import {PointInterface} from "../../Connection"; +import {PointInterface} from "../../Connexion/ConnexionModels"; import {Character} from "../Entity/Character"; /** diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts index d0ed2dad..519116ac 100644 --- a/front/src/Phaser/Game/AddPlayerInterface.ts +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -1,4 +1,4 @@ -import {PointInterface} from "../../Connection"; +import {PointInterface} from "../../Connexion/Connection"; export interface AddPlayerInterface { userId: number; diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index db119a13..34a0bdf6 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,9 +1,10 @@ import {GameScene} from "./GameScene"; import { StartMapInterface -} from "../../Connection"; +} from "../../Connexion/ConnexionModels"; import Axios from "axios"; import {API_URL} from "../../Enum/EnvironmentVariable"; +import {adminDataFetchPromise} from "../../register"; export interface HasMovedEvent { direction: string; @@ -29,13 +30,23 @@ export class GameManager { } loadStartMap() : Promise { - return Axios.get(`${API_URL}/start-map`) - .then((res) => { - return res.data; - }).catch((err) => { - console.error(err); - throw err; - }); + if (adminDataFetchPromise) { + return adminDataFetchPromise.then(data => { + return { + mapUrlStart: data.mapUrlStart, + startInstance: data.startInstance, + } + }) + } else { + //todo: remove this call, merge with the admin workflow? + return Axios.get(`${API_URL}/start-map`) + .then((res) => { + return res.data; + }).catch((err) => { + console.error(err); + throw err; + }); + } } getPlayerName(): string { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index f971a1e3..163eb3dc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,6 +1,5 @@ import {GameManager, gameManager, HasMovedEvent} from "./GameManager"; import { - Connection, GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserMovedInterface, @@ -8,7 +7,7 @@ import { PointInterface, PositionInterface, RoomJoinedMessageInterface -} from "../../Connection"; +} from "../../Connexion/ConnexionModels"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; import {DEBUG_MODE, JITSI_URL, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import { @@ -42,6 +41,8 @@ import {ActionableItem} from "../Items/ActionableItem"; import {UserInputManager} from "../UserInput/UserInputManager"; import {UserMovedMessage} from "../../Messages/generated/messages_pb"; import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils"; +import {connectionManager} from "../../Connexion/ConnectionManager"; +import {RoomConnection} from "../../Connexion/RoomConnection"; export enum Textures { @@ -100,9 +101,9 @@ export class GameScene extends Phaser.Scene implements CenterListener { pendingEvents: Queue = new Queue(); private initPosition: PositionInterface|null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); - private connection!: Connection; + private connection!: RoomConnection; private simplePeer!: SimplePeer; - private connectionPromise!: Promise + private connectionPromise!: Promise private connectionAnswerPromise: Promise; private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike) => void; // A promise that will resolve when the "create" method is called (signaling loading is ended) @@ -202,8 +203,10 @@ export class GameScene extends Phaser.Scene implements CenterListener { this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); - this.connectionPromise = Connection.createConnection(gameManager.getPlayerName(), gameManager.getCharacterSelected()).then((connection : Connection) => { + this.connectionPromise = connectionManager.connectToRoomSocket().then((connection : RoomConnection) => { this.connection = connection; + + this.connection.emitPlayerDetailsMessage(gameManager.getCharacterSelected()) connection.onUserJoins((message: MessageUserJoined) => { const userMessage: AddPlayerInterface = { @@ -778,7 +781,7 @@ export class GameScene extends Phaser.Scene implements CenterListener { this.createCollisionObject(); //join room - this.connectionPromise.then((connection: Connection) => { + this.connectionPromise.then((connection: RoomConnection) => { const camera = this.cameras.main; connection.joinARoom(this.RoomId, this.startX, diff --git a/front/src/Phaser/Game/PlayerMovement.ts b/front/src/Phaser/Game/PlayerMovement.ts index 56c4f113..eb1a5d1b 100644 --- a/front/src/Phaser/Game/PlayerMovement.ts +++ b/front/src/Phaser/Game/PlayerMovement.ts @@ -1,6 +1,6 @@ import {HasMovedEvent} from "./GameManager"; import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable"; -import {PositionInterface} from "../../Connection"; +import {PositionInterface} from "../../Connexion/ConnexionModels"; export class PlayerMovement { public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) { diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 6fc1cd54..6ac1ad47 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -1,12 +1,9 @@ import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; -import {ClickButton} from "../Components/ClickButton"; import Image = Phaser.GameObjects.Image; -import Rectangle = Phaser.GameObjects.Rectangle; -import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import {GameSceneInitInterface} from "../Game/GameScene"; -import {StartMapInterface} from "../../Connection"; -import {mediaManager, MediaManager} from "../../WebRtc/MediaManager"; +import {StartMapInterface} from "../../Connexion/ConnexionModels"; +import {mediaManager} from "../../WebRtc/MediaManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {SoundMeter} from "../Components/SoundMeter"; import {SoundMeterSprite} from "../Components/SoundMeterSprite"; diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 64285766..8d3c7ab1 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -3,8 +3,6 @@ import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; -import {GameSceneInitInterface} from "../Game/GameScene"; -import {StartMapInterface} from "../../Connection"; import {EnableCameraSceneName} from "./EnableCameraScene"; import {CustomizeSceneName} from "./CustomizeScene"; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 0fde84ae..b9c1c91a 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,9 +1,7 @@ import {PlayerAnimationNames} from "./Animation"; -import {GameScene, Textures} from "../Game/GameScene"; -import {MessageUserPositionInterface, PointInterface} from "../../Connection"; -import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; +import {GameScene} from "../Game/GameScene"; +import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {Character} from "../Entity/Character"; -import {OutlinePipeline} from "../Shaders/OutlinePipeline"; export const hasMovedEventName = "hasMoved"; diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 9c2022a6..3cbc4154 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -1,7 +1,7 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; -import {Connection} from "../Connection"; import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable"; +import {RoomConnection} from "../Connexion/RoomConnection"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -14,7 +14,7 @@ export class ScreenSharingPeer extends Peer { */ private isReceivingStream:boolean = false; - constructor(private userId: number, initiator: boolean, private connection: Connection) { + constructor(private userId: number, initiator: boolean, private connection: RoomConnection) { super({ initiator: initiator ? initiator : false, reconnectTimer: 10000, diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index ac603756..bafbc02f 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,9 +1,8 @@ import { - Connection, WebRtcDisconnectMessageInterface, WebRtcSignalReceivedMessageInterface, WebRtcStartMessageInterface -} from "../Connection"; +} from "../Connexion/ConnexionModels"; import { mediaManager, StartScreenSharingCallback, @@ -13,6 +12,7 @@ import { import * as SimplePeerNamespace from "simple-peer"; import {ScreenSharingPeer} from "./ScreenSharingPeer"; import {VideoPeer} from "./VideoPeer"; +import {RoomConnection} from "../Connexion/RoomConnection"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); export interface UserSimplePeerInterface{ @@ -31,7 +31,7 @@ export interface PeerConnectionListener { * This class manages connections to all the peers in the same group as me. */ export class SimplePeer { - private Connection: Connection; + private Connection: RoomConnection; private WebRtcRoomId: string; private Users: Array = new Array(); @@ -42,7 +42,7 @@ export class SimplePeer { private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly peerConnectionListeners: Array = new Array(); - constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { + constructor(Connection: RoomConnection, WebRtcRoomId: string = "test-webrtc") { this.Connection = Connection; this.WebRtcRoomId = WebRtcRoomId; // We need to go through this weird bound function pointer in order to be able to "free" this reference later. diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index e046ffe2..9331bea7 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -1,7 +1,7 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; -import {Connection} from "../Connection"; import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable"; +import {RoomConnection} from "../Connexion/RoomConnection"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -9,7 +9,7 @@ const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); * A peer connection used to transmit video / audio signals between 2 peers. */ export class VideoPeer extends Peer { - constructor(private userId: number, initiator: boolean, private connection: Connection) { + constructor(private userId: number, initiator: boolean, private connection: RoomConnection) { super({ initiator: initiator ? initiator : false, reconnectTimer: 10000, diff --git a/front/src/index.ts b/front/src/index.ts index 1f6b1d4a..8e235c7a 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -11,11 +11,10 @@ import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer; import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; import {CoWebsiteManager} from "./WebRtc/CoWebsiteManager"; -import {redirectIfToken} from "./register"; +import {connectionManager} from "./Connexion/ConnectionManager"; //CoWebsiteManager.loadCoWebsite('https://thecodingmachine.com'); -let connectionData //todo: do something with this data -redirectIfToken().then(res => connectionData = res); +connectionManager.init(); // Load Jitsi if the environment variable is set. if (JITSI_URL) { diff --git a/front/src/register.ts b/front/src/register.ts deleted file mode 100644 index 98fe0d1e..00000000 --- a/front/src/register.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Axios from "axios"; -import {API_URL} from "./Enum/EnvironmentVariable"; -declare let history:History; - -//todo: better naming -export interface ConnexionData { - organizationSlug: string, - worldSlug: string, - roomSlug: string, -} - -export async function redirectIfToken(): Promise { - const match = /\/register\/(.+)/.exec(window.location.toString()); - if (!match) { - return null - } - let res = null; - try { - res = await Axios.get(`${API_URL}/register/`+match[1]) - } catch (e) { - return null; - } - const organizationSlug = res.data.organizationSlug; - const worldSlug = res.data.worldSlug; - const roomSlug = res.data.roomSlug; - const connexionUrl = '/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug; - history.pushState({}, '', connexionUrl); - return {organizationSlug, worldSlug, roomSlug}; -} \ No newline at end of file From 3f9659ef3c75c643c356ef38e039b0a37bc53f8b Mon Sep 17 00:00:00 2001 From: arp Date: Mon, 28 Sep 2020 15:02:37 +0200 Subject: [PATCH 2/2] improvments --- back/src/App.ts | 3 - back/src/Controller/AuthenticateController.ts | 25 ++++--- back/src/Controller/IoSocketController.ts | 12 ++-- back/src/Controller/MapController.ts | 1 + back/src/Model/Websocket/ExSocketInterface.ts | 1 - front/src/Connexion/ConnectionManager.ts | 66 +++++++++++-------- front/src/Connexion/RoomConnection.ts | 4 +- front/src/Phaser/Game/AddPlayerInterface.ts | 2 +- front/src/Phaser/Game/GameManager.ts | 25 ++----- front/src/Phaser/Game/GameScene.ts | 4 +- 10 files changed, 72 insertions(+), 71 deletions(-) diff --git a/back/src/App.ts b/back/src/App.ts index a2aa91a5..545bdd91 100644 --- a/back/src/App.ts +++ b/back/src/App.ts @@ -7,7 +7,6 @@ import bodyParser = require('body-parser'); import * as http from "http"; import {MapController} from "./Controller/MapController"; import {PrometheusController} from "./Controller/PrometheusController"; -import {AdminController} from "./Controller/AdminController"; import {DebugController} from "./Controller/DebugController"; class App { @@ -17,7 +16,6 @@ class App { public authenticateController: AuthenticateController; public mapController: MapController; public prometheusController: PrometheusController; - private adminController: AdminController; private debugController: DebugController; constructor() { @@ -36,7 +34,6 @@ class App { this.authenticateController = new AuthenticateController(this.app); this.mapController = new MapController(this.app); this.prometheusController = new PrometheusController(this.app, this.ioSocketController); - this.adminController = new AdminController(this.app); this.debugController = new DebugController(this.app, this.ioSocketController); } diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index 26cc6ae5..d6ebe124 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -6,7 +6,14 @@ import { uuid } from 'uuidv4'; import Axios from "axios"; export interface TokenInterface { - name: string, + userUuid: string +} + +interface AdminApiData { + organizationSlug: string + worldSlug: string + roomSlug: string + mapUrlStart: string userUuid: string } @@ -35,20 +42,20 @@ export class AuthenticateController { return res.status(401).send('No admin backoffice set!'); } //todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case. - const response = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken, + const data = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken, { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } - ); + ).then((res): AdminApiData => res.data); - userUuid = response.data.userUuid; - mapUrlStart = response.data.mapUrlStart; - newUrl = this.getNewUrlOnAdminAuth(response.data) + userUuid = data.userUuid; + mapUrlStart = data.mapUrlStart; + newUrl = this.getNewUrlOnAdminAuth(data) } else { userUuid = uuid(); - mapUrlStart= URL_ROOM_STARTED; + mapUrlStart = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED; newUrl = null; } - const authToken = Jwt.sign({userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'}); + const authToken = Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '24h'}); return res.status(OK).send({ authToken, userUuid, @@ -64,7 +71,7 @@ export class AuthenticateController { }); } - getNewUrlOnAdminAuth(data:any): string { + private getNewUrlOnAdminAuth(data:AdminApiData): string { const organizationSlug = data.organizationSlug; const worldSlug = data.worldSlug; const roomSlug = data.roomSlug; diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 7111e7d2..3e18149f 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -121,18 +121,19 @@ export class IoSocketController { return next(new Error('Authentication error')); } Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => { + const tokenInterface = tokenDecoded as TokenInterface; if (err) { console.error('An authentication error happened, invalid JsonWebToken.', err); return next(new Error('Authentication error')); } - if (!this.isValidToken(tokenDecoded)) { + if (!this.isValidToken(tokenInterface)) { return next(new Error('Authentication error, invalid token structure')); } (socket as ExSocketInterface).token = socket.handshake.query.token; (socket as ExSocketInterface).userId = this.nextUserId; - (socket as ExSocketInterface).userUuid = tokenDecoded.userUuid; + (socket as ExSocketInterface).userUuid = tokenInterface.userUuid; this.nextUserId++; next(); }); @@ -141,11 +142,8 @@ export class IoSocketController { this.ioConnection(); } - private isValidToken(token: object): token is TokenInterface { - if (typeof((token as TokenInterface).userUuid) !== 'string') { - return false; - } - if (typeof((token as TokenInterface).name) !== 'string') { + private isValidToken(token: TokenInterface): boolean { + if (typeof(token.userUuid) !== 'string') { return false; } return true; diff --git a/back/src/Controller/MapController.ts b/back/src/Controller/MapController.ts index 58ce40a9..539eb9d7 100644 --- a/back/src/Controller/MapController.ts +++ b/back/src/Controller/MapController.ts @@ -3,6 +3,7 @@ import {Application, Request, Response} from "express"; import {OK} from "http-status-codes"; import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; +//todo: delete this export class MapController { App: Application; diff --git a/back/src/Model/Websocket/ExSocketInterface.ts b/back/src/Model/Websocket/ExSocketInterface.ts index ace374f4..3cacd7f5 100644 --- a/back/src/Model/Websocket/ExSocketInterface.ts +++ b/back/src/Model/Websocket/ExSocketInterface.ts @@ -1,7 +1,6 @@ import {Socket} from "socket.io"; import {PointInterface} from "./PointInterface"; import {Identificable} from "./Identificable"; -import {TokenInterface} from "../../Controller/AuthenticateController"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {BatchMessage, SubMessage} from "../../Messages/generated/messages_pb"; diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index cb70eef3..6f8e67a5 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -2,51 +2,61 @@ import Axios from "axios"; import {API_URL} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "./RoomConnection"; +interface LoginApiData { + authToken: string + userUuid: string + mapUrlStart: string + newUrl: string +} + class ConnectionManager { + private initPromise: Promise = Promise.reject(); private mapUrlStart: string|null = null; private authToken:string|null = null; private userUuid: string|null = null; - private userName:string|null = null; public async init(): Promise { const match = /\/register\/(.+)/.exec(window.location.toString()); const organizationMemberToken = match ? match[1] : null; - const res = await Axios.post(`${API_URL}/login`, {organizationMemberToken}); - this.authToken = res.data.authToken; - this.userUuid = res.data.userUuid; - this.mapUrlStart = res.data.mapUrlStart; - const newUrl = res.data.newUrl; + this.initPromise = Axios.post(`${API_URL}/login`, {organizationMemberToken}).then(res => res.data); + const data = await this.initPromise + this.authToken = data.authToken; + this.userUuid = data.userUuid; + this.mapUrlStart = data.mapUrlStart; + const newUrl = data.newUrl; if (newUrl) { history.pushState({}, '', newUrl); } } - public async setUserName(name:string): Promise { - //todo + public connectToRoomSocket(): Promise { + return new Promise((resolve, reject) => { + const connection = new RoomConnection(this.authToken as string); + connection.onConnectError((error: object) => { + console.log('An error occurred while connecting to socket server. Retrying'); + reject(error); + }); + resolve(connection); + }).catch((err) => { + // Let's retry in 4-6 seconds + return new Promise((resolve, reject) => { + setTimeout(() => { + //todo: allow a way to break recurrsion? + this.connectToRoomSocket().then((connection) => resolve(connection)); + }, 4000 + Math.floor(Math.random() * 2000) ); + }); + }); } - public connectToRoomSocket(): Promise { - return Axios.post(`${API_URL}/connectToSocket`, {authToken: this.authToken}).then((res) => { - return new Promise((resolve, reject) => { - const connection = new RoomConnection(res.data.roomToken); - connection.onConnectError((error: object) => { - console.log('An error occurred while connecting to socket server. Retrying'); - reject(error); - }); - resolve(connection); - }); - }) - .catch((err) => { - // Let's retry in 4-6 seconds - return new Promise((resolve, reject) => { - setTimeout(() => { - //todo: allow a way to break recurrsion? - this.connectToRoomSocket().then((connection) => resolve(connection)); - }, 4000 + Math.floor(Math.random() * 2000) ); - }); - }); + public getMapUrlStart(): Promise { + return this.initPromise.then(() => { + if (!this.mapUrlStart) { + throw new Error('No map url set!'); + } + return this.mapUrlStart; + }) } } diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 99ef7b4e..54519f60 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -83,9 +83,9 @@ export class RoomConnection implements RoomConnection { }) } - public emitPlayerDetailsMessage(characterLayersSelected: string[]) { + public emitPlayerDetailsMessage(userName: string, characterLayersSelected: string[]) { const message = new SetPlayerDetailsMessage(); - message.setName(name); + message.setName(userName); message.setCharacterlayersList(characterLayersSelected); this.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: number) => { this.userId = id; diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts index 519116ac..91563dd0 100644 --- a/front/src/Phaser/Game/AddPlayerInterface.ts +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -1,4 +1,4 @@ -import {PointInterface} from "../../Connexion/Connection"; +import {PointInterface} from "../../Connexion/ConnexionModels"; export interface AddPlayerInterface { userId: number; diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 34a0bdf6..960ce7e2 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -4,7 +4,7 @@ import { } from "../../Connexion/ConnexionModels"; import Axios from "axios"; import {API_URL} from "../../Enum/EnvironmentVariable"; -import {adminDataFetchPromise} from "../../register"; +import {connectionManager} from "../../Connexion/ConnectionManager"; export interface HasMovedEvent { direction: string; @@ -30,23 +30,12 @@ export class GameManager { } loadStartMap() : Promise { - if (adminDataFetchPromise) { - return adminDataFetchPromise.then(data => { - return { - mapUrlStart: data.mapUrlStart, - startInstance: data.startInstance, - } - }) - } else { - //todo: remove this call, merge with the admin workflow? - return Axios.get(`${API_URL}/start-map`) - .then((res) => { - return res.data; - }).catch((err) => { - console.error(err); - throw err; - }); - } + return connectionManager.getMapUrlStart().then(mapUrlStart => { + return { + mapUrlStart: mapUrlStart, + startInstance: "global", //todo: is this property still usefull? + } + }); } getPlayerName(): string { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 163eb3dc..425aaf36 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -205,8 +205,8 @@ export class GameScene extends Phaser.Scene implements CenterListener { this.connectionPromise = connectionManager.connectToRoomSocket().then((connection : RoomConnection) => { this.connection = connection; - - this.connection.emitPlayerDetailsMessage(gameManager.getCharacterSelected()) + + this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected()) connection.onUserJoins((message: MessageUserJoined) => { const userMessage: AddPlayerInterface = {