From aee06da7f9624a31dbdfcf5acfce5c588bde16c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 13 Oct 2020 16:46:46 +0200 Subject: [PATCH 1/4] Refactoring Room.ts to fetch map url automatically from ID --- back/src/Controller/IoSocketController.ts | 19 ++---- front/src/Connexion/ConnectionManager.ts | 16 ++--- front/src/Connexion/Room.ts | 73 ++++++++++++++++++++--- front/src/Connexion/RoomConnection.ts | 7 +-- front/src/Phaser/Game/GameManager.ts | 10 ++-- front/src/Url/UrlManager.ts | 7 --- messages/messages.proto | 18 +++--- 7 files changed, 94 insertions(+), 56 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 306b874e..d2a697c9 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -25,7 +25,6 @@ import { RoomJoinedMessage, ItemStateMessage, ServerToClientMessage, - SetUserIdMessage, SilentMessage, WebRtcSignalToClientMessage, WebRtcSignalToServerMessage, @@ -86,7 +85,7 @@ export class IoSocketController { this.ioConnection(); } - + ioConnection() { this.app.ws('/room', { @@ -114,7 +113,7 @@ export class IoSocketController { const websocketExtensions = req.getHeader('sec-websocket-extensions'); const roomId = query.roomId; - //todo: better validation: /\/_\/.*\/.*/ or /\/@\/.*\/.*\/.*/ + //todo: better validation: /\/_\/.*\/.*/ or /\/@\/.*\/.*\/.*/ if (typeof roomId !== 'string') { throw new Error('Undefined room ID: '); } @@ -153,7 +152,7 @@ export class IoSocketController { throw new Error('Client cannot acces this ressource.') } else { console.log('access granted for user '+userUuid+' and room '+roomId); - } + } } if (upgradeAborted.aborted) { @@ -228,16 +227,6 @@ export class IoSocketController { // Let's join the room this.handleJoinRoom(client, client.roomId, client.position, client.viewport, client.name, client.characterLayers); - - const setUserIdMessage = new SetUserIdMessage(); - setUserIdMessage.setUserid(client.userId); - - const serverToClientMessage = new ServerToClientMessage(); - serverToClientMessage.setSetuseridmessage(setUserIdMessage); - - if (!client.disconnecting) { - client.send(serverToClientMessage.serializeBinary().buffer, true); - } }, message: (ws, arrayBuffer, isBinary): void => { const client = ws as ExSocketInterface; @@ -363,6 +352,8 @@ export class IoSocketController { roomJoinedMessage.addItem(itemStateMessage); } + roomJoinedMessage.setCurrentuserid(client.userId); + const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index f41f26c0..fe30ec23 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -23,33 +23,33 @@ class ConnectionManager { const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data); this.localUser = new LocalUser(data.userUuid, data.authToken); localUserStore.saveUser(this.localUser); - + const organizationSlug = data.organizationSlug; const worldSlug = data.worldSlug; const roomSlug = data.roomSlug; urlManager.editUrlForRoom(roomSlug, organizationSlug, worldSlug); - - const room = new Room(window.location.pathname, data.mapUrlStart) + + const room = new Room(window.location.pathname); return Promise.resolve(room); } else if (connexionType === GameConnexionTypes.anonymous) { const localUser = localUserStore.getLocalUser(); - - if (localUser) { + + if (localUser && localUser.jwtToken && localUser.uuid) { this.localUser = localUser } else { const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data); this.localUser = new LocalUser(data.userUuid, data.authToken); localUserStore.saveUser(this.localUser); } - const room = new Room(window.location.pathname, urlManager.getAnonymousMapUrlStart()) + const room = new Room(window.location.pathname); return Promise.resolve(room); } else if (connexionType == GameConnexionTypes.organization) { const localUser = localUserStore.getLocalUser(); if (localUser) { this.localUser = localUser - //todo: ask the node api for the correct starting map Url from its slug - return Promise.reject('Case not handled: need to get the map\'s url from its slug'); + const room = new Room(window.location.pathname); + return Promise.resolve(room); } else { //todo: find some kind of fallback? return Promise.reject('Could not find a user in localstorage'); diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 36a8072b..744be2f8 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,10 +1,67 @@ +import Axios from "axios"; +import {API_URL} from "../Enum/EnvironmentVariable"; + export class Room { - public ID: string; - public url: string - - constructor(ID: string, url: string) { - this.ID = ID; - this.url = url; + public readonly id: string; + public readonly isPublic: boolean; + private mapUrl: string|undefined; + //public url: string + + constructor(id: string/*, url: string*/) { + if (id.startsWith('/')) { + id = id.substr(1); + } + this.id = id; + if (id.startsWith('_/')) { + this.isPublic = true; + } else if (id.startsWith('@/')) { + this.isPublic = false; + } else { + throw new Error('Invalid room ID'); + } } - -} \ No newline at end of file + + public async getMapUrl(): Promise { + return new Promise(async (resolve, reject) => { + if (this.mapUrl !== undefined) { + resolve(this.mapUrl); + return; + } + + if (this.isPublic) { + const match = /_\/[^\/]+\/(.+)/.exec(this.id) + if (!match) throw new Error('Could not extract url from "'+this.id+'"'); + this.mapUrl = window.location.protocol+'//'+match[1]; + resolve(this.mapUrl); + return; + } else { + // We have a private ID, we need to query the map URL from the server. + const urlParts = this.parsePrivateUrl(this.id); + + const data:any = await Axios.get(`${API_URL}/map`, { + params: urlParts + }); + + console.log('Map ', this.id, ' resolves to URL ', data.data.mapUrl); + resolve(data.data.mapUrl); + return; + } + }); + } + + private parsePrivateUrl(url: string): { organizationSlug: string, worldSlug: string, roomSlug?: string } { + const regex = /@\/([^\/]+)\/([^\/]+)(?:\/([^\/]*))?/gm; + const match = regex.exec(url); + if (!match) { + throw new Error('Invalid URL '+url); + } + let results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { + organizationSlug: match[1], + worldSlug: match[2], + } + if (match[3] !== undefined) { + results.roomSlug = match[3]; + } + return results; + } +} diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index fd9410c4..48f6f9f1 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -11,7 +11,6 @@ import { RoomJoinedMessage, ServerToClientMessage, SetPlayerDetailsMessage, - SetUserIdMessage, SilentMessage, StopGlobalMessage, UserJoinedMessage, UserLeftMessage, @@ -58,7 +57,7 @@ export class RoomConnection implements RoomConnection { let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://'); url += '/room'; url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); - url += '?token='+(token ?encodeURIComponent(token):''); + url += '&token='+(token ?encodeURIComponent(token):''); url += '&name='+encodeURIComponent(name); for (const layer of characterLayers) { url += '&characterLayers='+encodeURIComponent(layer); @@ -124,13 +123,13 @@ export class RoomConnection implements RoomConnection { items[item.getItemid()] = JSON.parse(item.getStatejson()); } + this.userId = roomJoinedMessage.getCurrentuserid(); + this.dispatch(EventMessage.START_ROOM, { users, groups, items }); - } else if (message.hasSetuseridmessage()) { - this.userId = (message.getSetuseridmessage() as SetUserIdMessage).getUserid(); } else if (message.hasErrormessage()) { console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage()); } else if (message.hasWebrtcsignaltoclientmessage()) { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 01eee687..25721a96 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -17,7 +17,8 @@ export class GameManager { public async init(scenePlugin: Phaser.Scenes.ScenePlugin) { this.startRoom = await connectionManager.initGameConnexion(); - this.loadMap(this.startRoom.url, this.startRoom.ID, scenePlugin); + const url = await this.startRoom.getMapUrl(); + this.loadMap(url, this.startRoom.id, scenePlugin); } public setPlayerName(name: string): void { @@ -58,9 +59,10 @@ export class GameManager { return mapUrlStart.substring(startPos, endPos); } - public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) { - console.log('Starting scene '+this.startRoom.url); - scenePlugin.start(this.startRoom.url, {startLayerName: 'global'}); + public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) { + const url = await this.startRoom.getMapUrl(); + console.log('Starting scene '+url); + scenePlugin.start(url, {startLayerName: 'global'}); } } diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 85168e7c..fb7157c8 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -23,13 +23,6 @@ class UrlManager { } } - public getAnonymousMapUrlStart():string { - const match = /\/_\/global\/(.+)/.exec(window.location.pathname.toString()) - if (!match) throw new Error('Could not extract startmap url from'+window.location.pathname); - return window.location.protocol+'//'+match[1]; - - } - public getOrganizationToken(): string|null { const match = /\/register\/(.+)/.exec(window.location.pathname.toString()); return match ? match [1] : null; diff --git a/messages/messages.proto b/messages/messages.proto index 63c3ddaf..a5134b56 100644 --- a/messages/messages.proto +++ b/messages/messages.proto @@ -129,10 +129,6 @@ message ErrorMessage { string message = 1; } -message SetUserIdMessage { - int32 userId = 1; -} - message ItemStateMessage { int32 itemId = 1; string stateJson = 2; @@ -142,6 +138,7 @@ message RoomJoinedMessage { repeated UserJoinedMessage user = 1; repeated GroupUpdateMessage group = 2; repeated ItemStateMessage item = 3; + int32 currentUserId = 4; } message WebRtcStartMessage { @@ -164,12 +161,11 @@ message ServerToClientMessage { BatchMessage batchMessage = 1; ErrorMessage errorMessage = 2; RoomJoinedMessage roomJoinedMessage = 3; - SetUserIdMessage setUserIdMessage = 4; // TODO: merge this with RoomJoinedMessage ? - WebRtcStartMessage webRtcStartMessage = 5; - WebRtcSignalToClientMessage webRtcSignalToClientMessage = 6; - WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 7; - WebRtcDisconnectMessage webRtcDisconnectMessage = 8; - PlayGlobalMessage playGlobalMessage = 9; - StopGlobalMessage stopGlobalMessage = 10; + WebRtcStartMessage webRtcStartMessage = 4; + WebRtcSignalToClientMessage webRtcSignalToClientMessage = 5; + WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 6; + WebRtcDisconnectMessage webRtcDisconnectMessage = 7; + PlayGlobalMessage playGlobalMessage = 8; + StopGlobalMessage stopGlobalMessage = 9; } } From ec93891c6ba63e3f474158e6f7534710138aaaeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 13 Oct 2020 17:08:24 +0200 Subject: [PATCH 2/4] Using Room class to load GameScene --- front/src/Connexion/Room.ts | 3 +-- front/src/Phaser/Game/GameManager.ts | 8 +++++--- front/src/Phaser/Game/GameScene.ts | 8 +++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 744be2f8..51ccb858 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -5,9 +5,8 @@ export class Room { public readonly id: string; public readonly isPublic: boolean; private mapUrl: string|undefined; - //public url: string - constructor(id: string/*, url: string*/) { + constructor(id: string) { if (id.startsWith('/')) { id = id.substr(1); } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 25721a96..7cc5c0c4 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -17,8 +17,7 @@ export class GameManager { public async init(scenePlugin: Phaser.Scenes.ScenePlugin) { this.startRoom = await connectionManager.initGameConnexion(); - const url = await this.startRoom.getMapUrl(); - this.loadMap(url, this.startRoom.id, scenePlugin); + await this.loadMap(this.startRoom, scenePlugin); } public setPlayerName(name: string): void { @@ -42,8 +41,11 @@ export class GameManager { } - public loadMap(mapUrl: string, roomID: string, scenePlugin: Phaser.Scenes.ScenePlugin): void { + public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise { + const roomID = room.id; + const mapUrl = await room.getMapUrl(); console.log('Loading map '+roomID+' at url '+mapUrl); + const gameIndex = scenePlugin.getIndex(mapUrl); if(gameIndex === -1){ const game : Phaser.Scene = GameScene.createFromUrl(mapUrl, roomID); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a22d973a..8e8cb8d0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -46,6 +46,7 @@ import {RoomConnection} from "../../Connexion/RoomConnection"; import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager"; import {ResizableScene} from "../Login/ResizableScene"; +import {Room} from "../../Connexion/Room"; export enum Textures { @@ -690,8 +691,13 @@ export class GameScene extends ResizableScene implements CenterListener { } // TODO: eventually compute a relative URL + + // TODO: handle /@/ URL CASES! + const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href; - gameManager.loadMap(absoluteExitSceneUrl, instance, this.scene); + const absoluteExitSceneUrlWithoutProtocol = absoluteExitSceneUrl.toString().substr(absoluteExitSceneUrl.toString().indexOf('://')+3); + const roomId = '_/'+instance+'/'+absoluteExitSceneUrlWithoutProtocol; + gameManager.loadMap(new Room(roomId), this.scene); const exitSceneKey = instance; const tiles : number[] = layer.data as number[]; From 45a2721c5c80fc3351b0abe8c7ef63d93f701977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 13 Oct 2020 17:20:20 +0200 Subject: [PATCH 3/4] Fixing code post rebase --- front/src/Connexion/ConnectionManager.ts | 18 +++++++++++------- front/src/Url/UrlManager.ts | 7 +++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index fe30ec23..c3b4c39b 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -31,7 +31,7 @@ class ConnectionManager { const room = new Room(window.location.pathname); return Promise.resolve(room); - } else if (connexionType === GameConnexionTypes.anonymous) { + } else if (connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) { const localUser = localUserStore.getLocalUser(); if (localUser && localUser.jwtToken && localUser.uuid) { @@ -41,7 +41,14 @@ class ConnectionManager { this.localUser = new LocalUser(data.userUuid, data.authToken); localUserStore.saveUser(this.localUser); } - const room = new Room(window.location.pathname); + let roomId: string + if (connexionType === GameConnexionTypes.empty) { + const defaultMapUrl = window.location.host.replace('play.', 'maps.') + URL_ROOM_STARTED; + roomId = urlManager.editUrlForRoom(defaultMapUrl, null, null); + } else { + roomId = window.location.pathname; + } + const room = new Room(roomId); return Promise.resolve(room); } else if (connexionType == GameConnexionTypes.organization) { const localUser = localUserStore.getLocalUser(); @@ -55,11 +62,8 @@ class ConnectionManager { return Promise.reject('Could not find a user in localstorage'); } } - - //todo: cleaner way to handle the default case - const defaultMapUrl = window.location.host.replace('api.', 'maps.') + URL_ROOM_STARTED; - const defaultRoomId = urlManager.editUrlForRoom(URL_ROOM_STARTED, null, null); - return Promise.resolve(new Room(defaultRoomId, defaultMapUrl)); + + return Promise.reject('Invalid URL'); } public initBenchmark(): void { diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index fb7157c8..f7eeacce 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -3,6 +3,7 @@ export enum GameConnexionTypes { anonymous=1, organization, register, + empty, unknown, } @@ -17,9 +18,11 @@ class UrlManager { } else if (url.includes('@/')) { return GameConnexionTypes.organization; } else if(url.includes('register/')) { - return GameConnexionTypes.register + return GameConnexionTypes.register; + } else if(url === '/') { + return GameConnexionTypes.empty; } else { - return GameConnexionTypes.unknown + return GameConnexionTypes.unknown; } } From a2c750dea2c462c3a240b6fe6d2430564b124e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 13 Oct 2020 17:30:53 +0200 Subject: [PATCH 4/4] Fix linter --- front/src/Connexion/Room.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 51ccb858..f01a74d0 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -21,14 +21,14 @@ export class Room { } public async getMapUrl(): Promise { - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { if (this.mapUrl !== undefined) { resolve(this.mapUrl); return; } if (this.isPublic) { - const match = /_\/[^\/]+\/(.+)/.exec(this.id) + const match = /_\/[^/]+\/(.+)/.exec(this.id); if (!match) throw new Error('Could not extract url from "'+this.id+'"'); this.mapUrl = window.location.protocol+'//'+match[1]; resolve(this.mapUrl); @@ -37,24 +37,25 @@ export class Room { // We have a private ID, we need to query the map URL from the server. const urlParts = this.parsePrivateUrl(this.id); - const data:any = await Axios.get(`${API_URL}/map`, { + Axios.get(`${API_URL}/map`, { params: urlParts + }).then(({data}) => { + console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); + resolve(data.mapUrl); + return; }); - console.log('Map ', this.id, ' resolves to URL ', data.data.mapUrl); - resolve(data.data.mapUrl); - return; } }); } private parsePrivateUrl(url: string): { organizationSlug: string, worldSlug: string, roomSlug?: string } { - const regex = /@\/([^\/]+)\/([^\/]+)(?:\/([^\/]*))?/gm; + const regex = /@\/([^/]+)\/([^/]+)(?:\/([^/]*))?/gm; const match = regex.exec(url); if (!match) { throw new Error('Invalid URL '+url); } - let results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { + const results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { organizationSlug: match[1], worldSlug: match[2], }