diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index 84cfd97f..984d7445 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -1,22 +1,14 @@ -import Jwt from "jsonwebtoken"; -import {ADMIN_API_TOKEN, ADMIN_API_URL, SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." +import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import { v4 } from 'uuid'; import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; import {BaseController} from "./BaseController"; -import Axios from "axios"; +import {adminApi, AdminApiData} from "../Services/AdminApi"; +import {jwtTokenManager} from "../Services/JWTTokenManager"; export interface TokenInterface { userUuid: string } -interface AdminApiData { - organizationSlug: string - worldSlug: string - roomSlug: string - mapUrlStart: string - userUuid: string -} - export class AuthenticateController extends BaseController { constructor(private App : TemplatedApp) { @@ -44,6 +36,7 @@ export class AuthenticateController extends BaseController { //todo: what to do if the organizationMemberToken is already used? const organizationMemberToken:string|null = param.organizationMemberToken; + const mapSlug:string|null = param.mapSlug; try { let userUuid; @@ -51,24 +44,22 @@ export class AuthenticateController extends BaseController { let newUrl: string|null = 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 data = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken, - { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } - ).then((res): AdminApiData => res.data); + const data = await adminApi.fetchMemberDataByToken(organizationMemberToken); userUuid = data.userUuid; mapUrlStart = data.mapUrlStart; newUrl = this.getNewUrlOnAdminAuth(data) + } else if (mapSlug !== null) { + userUuid = v4(); + mapUrlStart = mapSlug; + newUrl = null; } else { userUuid = v4(); mapUrlStart = host.replace('api.', 'maps.') + URL_ROOM_STARTED; - newUrl = null; + newUrl = '_/global/'+mapUrlStart; } - const authToken = Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '24h'}); + const authToken = jwtTokenManager.createJWTToken(userUuid); res.writeStatus("200 OK").end(JSON.stringify({ authToken, userUuid, diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index e9f97ea6..7ef0d811 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -1,23 +1,11 @@ -import * as http from "http"; -import {MessageUserPosition, Point} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.." import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." -import Jwt, {JsonWebTokenError} from "jsonwebtoken"; -import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS, ALLOW_ARTILLERY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." -import {World} from "../Model/World"; +import {MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." +import {GameRoom} from "../Model/GameRoom"; import {Group} from "../Model/Group"; import {User} from "../Model/User"; import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage"; -import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined"; -import si from "systeminformation"; import {Gauge} from "prom-client"; -import {TokenInterface} from "../Controller/AuthenticateController"; -import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage"; import {PointInterface} from "../Model/Websocket/PointInterface"; -import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; -import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; -import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage"; -import { v4 as uuidv4 } from 'uuid'; -import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface"; import {Movable} from "../Model/Movable"; import { PositionMessage, @@ -33,7 +21,6 @@ import { ItemEventMessage, ViewportMessage, ClientToServerMessage, - JoinRoomMessage, ErrorMessage, RoomJoinedMessage, ItemStateMessage, @@ -42,14 +29,19 @@ import { SilentMessage, WebRtcSignalToClientMessage, WebRtcSignalToServerMessage, - WebRtcStartMessage, WebRtcDisconnectMessage, PlayGlobalMessage + WebRtcStartMessage, + WebRtcDisconnectMessage, + PlayGlobalMessage, } from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb"; import Direction = PositionMessage.Direction; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; -import {App, HttpRequest, TemplatedApp, WebSocket} from "uWebSockets.js" +import {TemplatedApp} from "uWebSockets.js" import {parse} from "query-string"; import {cpuTracker} from "../Services/CpuTracker"; +import {ViewportInterface} from "../Model/Websocket/ViewportMessage"; +import {jwtTokenManager} from "../Services/JWTTokenManager"; +import {adminApi} from "../Services/AdminApi"; function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { socket.batchedMessages.addPayload(payload); @@ -71,7 +63,7 @@ function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { } export class IoSocketController { - private Worlds: Map = new Map(); + private Worlds: Map = new Map(); private sockets: Map = new Map(); private nbClientsGauge: Gauge; private nbClientsPerRoomGauge: Gauge; @@ -93,92 +85,10 @@ export class IoSocketController { this.ioConnection(); } - private isValidToken(token: object): token is TokenInterface { - if (typeof((token as TokenInterface).userUuid) !== 'string') { - return false; - } - return true; - } - - /** - * - * @param token - */ -/* searchClientByToken(token: string): ExSocketInterface | null { - const clients: ExSocketInterface[] = Object.values(this.Io.sockets.sockets) as ExSocketInterface[]; - for (let i = 0; i < clients.length; i++) { - const client = clients[i]; - if (client.token !== token) { - continue - } - return client; - } - return null; - }*/ - - private async authenticate(req: HttpRequest): Promise<{ token: string, userUuid: string }> { - //console.log(socket.handshake.query.token); - - const query = parse(req.getQuery()); - - if (!query.token) { - throw new Error('An authentication error happened, a user tried to connect without a token.'); - } - - const token = query.token; - if (typeof(token) !== "string") { - throw new Error('Token is expected to be a string'); - } - - - if(token === 'test') { - if (ALLOW_ARTILLERY) { - return { - token, - userUuid: uuidv4() - } - } else { - throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"); - } - } - - /*if(this.searchClientByToken(socket.handshake.query.token)){ - console.error('An authentication error happened, a user tried to connect while its token is already connected.'); - return next(new Error('Authentication error')); - }*/ - - const promise = new Promise<{ token: string, userUuid: string }>((resolve, reject) => { - Jwt.verify(token, SECRET_KEY, {},(err, tokenDecoded) => { - if (err) { - console.error('An authentication error happened, invalid JsonWebToken.', err); - reject(new Error('An authentication error happened, invalid JsonWebToken. '+err.message)); - return; - } - if (tokenDecoded === undefined) { - console.error('Empty token found.'); - reject(new Error('Empty token found.')); - return; - } - const tokenInterface = tokenDecoded as TokenInterface; - - if (!this.isValidToken(tokenInterface)) { - reject(new Error('Authentication error, invalid token structure.')); - return; - } - - resolve({ - token, - userUuid: tokenInterface.userUuid - }); - }); - }); - - return promise; - } + ioConnection() { - this.app.ws('/*', { - + this.app.ws('/room/*', { /* Options */ //compression: uWS.SHARED_COMPRESSOR, maxPayloadLength: 16 * 1024 * 1024, @@ -187,7 +97,6 @@ export class IoSocketController { upgrade: (res, req, context) => { //console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!'); (async () => { - /* Keep track of abortions */ const upgradeAborted = {aborted: false}; @@ -197,7 +106,47 @@ export class IoSocketController { }); try { - const result = await this.authenticate(req); + const url = req.getUrl(); + const query = parse(req.getQuery()); + const websocketKey = req.getHeader('sec-websocket-key'); + const websocketProtocol = req.getHeader('sec-websocket-protocol'); + const websocketExtensions = req.getHeader('sec-websocket-extensions'); + + const roomId = req.getUrl().substr(6); + + const token = query.token; + const x = Number(query.x); + const y = Number(query.y); + const top = Number(query.top); + const bottom = Number(query.bottom); + const left = Number(query.left); + const right = Number(query.right); + const name = query.name; + if (typeof name !== 'string') { + throw new Error('Expecting name'); + } + if (name === '') { + throw new Error('No empty name'); + } + let characterLayers = query.characterLayers; + if (characterLayers === null) { + throw new Error('Expecting skin'); + } + if (typeof characterLayers === 'string') { + characterLayers = [ characterLayers ]; + } + + + const userUuid = await jwtTokenManager.getUserUuidFromToken(token); + console.log('uuid', userUuid); + + const isGranted = await adminApi.memberIsGrantedAccessToRoom(userUuid, roomId); + if (!isGranted) { + console.log('access not granted for user '+userUuid+' and room '+roomId); + throw new Error('Client cannot acces this ressource.') + } else { + console.log('access granted for user '+userUuid+' and room '+roomId); + } if (upgradeAborted.aborted) { console.log("Ouch! Client disconnected before we could upgrade it!"); @@ -208,22 +157,37 @@ export class IoSocketController { /* This immediately calls open handler, you must not use res after this call */ res.upgrade({ // Data passed here is accessible on the "websocket" socket object. - url: req.getUrl(), - token: result.token, - userUuid: result.userUuid + url, + token, + userUuid, + roomId, + name, + characterLayers, + position: { + x: x, + y: y, + direction: 'down', + moving: false + } as PointInterface, + viewport: { + top, + right, + bottom, + left + } }, /* Spell these correctly */ - req.getHeader('sec-websocket-key'), - req.getHeader('sec-websocket-protocol'), - req.getHeader('sec-websocket-extensions'), + websocketKey, + websocketProtocol, + websocketExtensions, context); } catch (e) { if (e instanceof Error) { - console.warn(e.message); + console.log(e.message); res.writeStatus("401 Unauthorized").end(e.message); } else { - console.warn(e); + console.log(e); res.writeStatus("500 Internal Server Error").end('An error occurred'); } return; @@ -243,20 +207,35 @@ export class IoSocketController { emitInBatch(client, payload); } client.disconnecting = false; + + client.name = ws.name; + client.characterLayers = ws.characterLayers; + client.roomId = ws.roomId; + this.sockets.set(client.userId, client); // Let's log server load when a user joins this.nbClientsGauge.inc(); console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)'); + // 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) => { + message: (ws, arrayBuffer, isBinary): void => { const client = ws as ExSocketInterface; const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer)); - if (message.hasJoinroommessage()) { - this.handleJoinRoom(client, message.getJoinroommessage() as JoinRoomMessage); - } else if (message.hasViewportmessage()) { + if (message.hasViewportmessage()) { this.handleViewport(client, message.getViewportmessage() as ViewportMessage); } else if (message.hasUsermovesmessage()) { this.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage); @@ -333,26 +312,12 @@ export class IoSocketController { console.warn(message); } - private handleJoinRoom(Client: ExSocketInterface, message: JoinRoomMessage): void { + private handleJoinRoom(client: ExSocketInterface, roomId: string, position: PointInterface, viewport: ViewportInterface, name: string, characterLayers: string[]): void { try { - /*if (!isJoinRoomMessageInterface(message.toObject())) { - console.log(message.toObject()) - this.emitError(Client, 'Invalid JOIN_ROOM message received: ' + message.toObject().toString()); - return; - }*/ - const roomId = message.getRoomid(); - - if (Client.roomId === roomId) { - return; - } - - //leave previous room - //this.leaveRoom(Client); // Useless now, there is only one room per connection - //join new previous room - const world = this.joinRoom(Client, roomId, ProtobufUtils.toPointInterface(message.getPosition() as PositionMessage)); + const gameRoom = this.joinRoom(client, roomId, position); - const things = world.setViewport(Client, (message.getViewport() as ViewportMessage).toObject()); + const things = gameRoom.setViewport(client, viewport); const roomJoinedMessage = new RoomJoinedMessage(); @@ -382,7 +347,7 @@ export class IoSocketController { } } - for (const [itemId, item] of world.getItemsState().entries()) { + for (const [itemId, item] of gameRoom.getItemsState().entries()) { const itemStateMessage = new ItemStateMessage(); itemStateMessage.setItemid(itemId); itemStateMessage.setStatejson(JSON.stringify(item)); @@ -393,8 +358,8 @@ export class IoSocketController { const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage); - if (!Client.disconnecting) { - Client.send(serverToClientMessage.serializeBinary().buffer, true); + if (!client.disconnecting) { + client.send(serverToClientMessage.serializeBinary().buffer, true); } } catch (e) { console.error('An error occurred on "join_room" event'); @@ -480,6 +445,7 @@ export class IoSocketController { } } + // Useless now, will be useful again if we allow editing details in game private handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) { const playerDetails = { name: playerDetailsMessage.getName(), @@ -493,16 +459,6 @@ export class IoSocketController { client.name = playerDetails.name; client.characterLayers = playerDetails.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); - } } private handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) { @@ -600,7 +556,7 @@ export class IoSocketController { if(Client.roomId){ try { //user leave previous world - const world: World | undefined = this.Worlds.get(Client.roomId); + const world: GameRoom | undefined = this.Worlds.get(Client.roomId); if (world) { world.leave(Client); if (world.isEmpty()) { @@ -616,17 +572,17 @@ export class IoSocketController { } } - private joinRoom(Client : ExSocketInterface, roomId: string, position: PointInterface): World { + private joinRoom(client : ExSocketInterface, roomId: string, position: PointInterface): GameRoom { + //join user in room - //Client.join(roomId); this.nbClientsPerRoomGauge.inc({ room: roomId }); - Client.roomId = roomId; - Client.position = position; + client.roomId = roomId; + client.position = position; //check and create new world for a room let world = this.Worlds.get(roomId) if(world === undefined){ - world = new World((user1: User, group: Group) => { + world = new GameRoom((user1: User, group: Group) => { this.joinWebRtcRoom(user1, group); }, (user1: User, group: Group) => { this.disConnectedUser(user1, group); @@ -689,10 +645,10 @@ export class IoSocketController { // Dispatch groups position to newly connected user world.getGroups().forEach((group: Group) => { - this.emitCreateUpdateGroupEvent(Client, group); + this.emitCreateUpdateGroupEvent(client, group); }); //join world - world.join(Client, Client.position); + world.join(client, client.position); return world; } @@ -882,7 +838,7 @@ export class IoSocketController { } - public getWorlds(): Map { + public getWorlds(): Map { return this.Worlds; } } diff --git a/back/src/Model/World.ts b/back/src/Model/GameRoom.ts similarity index 68% rename from back/src/Model/World.ts rename to back/src/Model/GameRoom.ts index c276d04e..1f438e61 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/GameRoom.ts @@ -1,12 +1,10 @@ -import {MessageUserPosition, Point} from "./Websocket/MessageUserPosition"; import {PointInterface} from "./Websocket/PointInterface"; import {Group} from "./Group"; -import {Distance} from "./Distance"; import {User} from "./User"; import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {PositionInterface} from "_Model/PositionInterface"; import {Identificable} from "_Model/Websocket/Identificable"; -import {EntersCallback, LeavesCallback, MovesCallback, Zone} from "_Model/Zone"; +import {EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone"; import {PositionNotifier} from "./PositionNotifier"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {Movable} from "_Model/Movable"; @@ -14,7 +12,7 @@ import {Movable} from "_Model/Movable"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; -export class World { +export class GameRoom { private readonly minDistance: number; private readonly groupRadius: number; @@ -123,7 +121,7 @@ export class World { } else { // If the user is part of a group: // should he leave the group? - const distance = World.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition()); + const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition()); if (distance > this.groupRadius) { this.leaveGroup(user); } @@ -199,53 +197,19 @@ export class World { return; } - const distance = World.computeDistance(user, currentUser); // compute distance between peers. + const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers. if(distance <= minimumDistanceFound && distance <= this.minDistance) { minimumDistanceFound = distance; matchingItem = currentUser; } - /*if (typeof currentUser.group === 'undefined' || !currentUser.group.isFull()) { - // We found a user we can bind to. - return; - }*/ - /* - if(context.groups.length > 0) { - - context.groups.forEach(group => { - if(group.isPartOfGroup(userPosition)) { // Is the user in a group ? - if(group.isStillIn(userPosition)) { // Is the user leaving the group ? (is the user at more than max distance of each player) - - // Should we split the group? (is each player reachable from the current player?) - // This is needed if - // A <==> B <==> C <===> D - // becomes A <==> B <=====> C <> D - // If C moves right, the distance between B and C is too great and we must form 2 groups - - } - } else { - // If the user is in no group - // Is there someone in a group close enough and with room in the group ? - } - }); - - } else { - // Aucun groupe n'existe donc je stock les users assez proches de moi - let dist: Distance = { - distance: distance, - first: userPosition, - second: user // TODO: convertir en messageUserPosition - } - usersToBeGroupedWith.push(dist); - } - */ }); this.groups.forEach((group: Group) => { if (group.isFull()) { return; } - const distance = World.computeDistanceBetweenPositions(user.getPosition(), group.getPosition()); + const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition()); if(distance <= minimumDistanceFound && distance <= this.groupRadius) { minimumDistanceFound = distance; matchingItem = group; @@ -275,66 +239,7 @@ export class World { return this.itemsState; } - /*getDistancesBetweenGroupUsers(group: Group): Distance[] - { - let i = 0; - let users = group.getUsers(); - let distances: Distance[] = []; - users.forEach(function(user1, key1) { - users.forEach(function(user2, key2) { - if(key1 < key2) { - distances[i] = { - distance: World.computeDistance(user1, user2), - first: user1, - second: user2 - }; - i++; - } - }); - }); - - distances.sort(World.compareDistances); - - return distances; - } - - filterGroup(distances: Distance[], group: Group): void - { - let users = group.getUsers(); - let usersToRemove = false; - let groupTmp: MessageUserPosition[] = []; - distances.forEach(dist => { - if(dist.distance <= World.MIN_DISTANCE) { - let users = [dist.first]; - let usersbis = [dist.second] - groupTmp.push(dist.first); - groupTmp.push(dist.second); - } else { - usersToRemove = true; - } - }); - - if(usersToRemove) { - // Detecte le ou les users qui se sont fait sortir du groupe - let difference = users.filter(x => !groupTmp.includes(x)); - - // TODO : Notify users un difference that they have left the group - } - - let newgroup = new Group(groupTmp); - this.groups.push(newgroup); - } - - private static compareDistances(distA: Distance, distB: Distance): number - { - if (distA.distance < distB.distance) { - return -1; - } - if (distA.distance > distB.distance) { - return 1; - } - return 0; - }*/ + setViewport(socket : Identificable, viewport: ViewportInterface): Movable[] { const user = this.users.get(socket.userId); if(typeof user === 'undefined') { diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index a67383f0..9afa9764 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -1,4 +1,4 @@ -import { ConnectCallback, DisconnectCallback } from "./World"; +import { ConnectCallback, DisconnectCallback } from "./GameRoom"; import { User } from "./User"; import {PositionInterface} from "_Model/PositionInterface"; import {Movable} from "_Model/Movable"; diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts new file mode 100644 index 00000000..79a68810 --- /dev/null +++ b/back/src/Services/AdminApi.ts @@ -0,0 +1,41 @@ +import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; +import Axios, {AxiosError} from "axios"; + +export interface AdminApiData { + organizationSlug: string + worldSlug: string + roomSlug: string + mapUrlStart: string + userUuid: string +} + +class AdminApi { + + async fetchMemberDataByToken(organizationMemberToken: string): Promise { + if (!ADMIN_API_URL) { + return Promise.reject('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 res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken, + { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } + ) + return res.data; + } + + async memberIsGrantedAccessToRoom(memberId: string, roomId: string): Promise { + if (!ADMIN_API_URL) { + return Promise.reject('No admin backoffice set!'); + } + try { + const res = await Axios.get(ADMIN_API_URL+'/api/member/is-granted-access', + { headers: {"Authorization" : `${ADMIN_API_TOKEN}`}, params: {memberId, roomIdentifier: roomId} } + ) + return !!res.data; + } catch (e) { + console.log(e.message) + return false; + } + } +} + +export const adminApi = new AdminApi(); \ No newline at end of file diff --git a/back/src/Services/JWTTokenManager.ts b/back/src/Services/JWTTokenManager.ts new file mode 100644 index 00000000..905e0ac6 --- /dev/null +++ b/back/src/Services/JWTTokenManager.ts @@ -0,0 +1,60 @@ +import {ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable"; +import {uuid} from "uuidv4"; +import Jwt from "jsonwebtoken"; +import {TokenInterface} from "../Controller/AuthenticateController"; + +class JWTTokenManager { + + public createJWTToken(userUuid: string) { + return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '24h'}); + } + + public async getUserUuidFromToken(token: unknown): Promise { + + if (!token) { + throw new Error('An authentication error happened, a user tried to connect without a token.'); + } + if (typeof(token) !== "string") { + throw new Error('Token is expected to be a string'); + } + + + if(token === 'test') { + if (ALLOW_ARTILLERY) { + return uuid(); + } else { + throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"); + } + } + + return new Promise((resolve, reject) => { + Jwt.verify(token, SECRET_KEY, {},(err, tokenDecoded) => { + const tokenInterface = tokenDecoded as TokenInterface; + if (err) { + console.error('An authentication error happened, invalid JsonWebToken.', err); + reject(new Error('An authentication error happened, invalid JsonWebToken. '+err.message)); + return; + } + if (tokenDecoded === undefined) { + console.error('Empty token found.'); + reject(new Error('Empty token found.')); + return; + } + + if (!this.isValidToken(tokenInterface)) { + reject(new Error('Authentication error, invalid token structure.')); + return; + } + + resolve(tokenInterface.userUuid); + }); + }); + } + + private isValidToken(token: object): token is TokenInterface { + return !(typeof((token as TokenInterface).userUuid) !== 'string'); + } + +} + +export const jwtTokenManager = new JWTTokenManager(); \ No newline at end of file diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index 253283af..0f556866 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -1,5 +1,5 @@ import "jasmine"; -import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World"; +import {GameRoom, ConnectCallback, DisconnectCallback } from "_Model/GameRoom"; import {Point} from "../src/Model/Websocket/MessageUserPosition"; import { Group } from "../src/Model/Group"; import {PositionNotifier} from "../src/Model/PositionNotifier"; diff --git a/back/tests/WorldTest.ts b/back/tests/WorldTest.ts index 8d3b1a2d..5e06414c 100644 --- a/back/tests/WorldTest.ts +++ b/back/tests/WorldTest.ts @@ -1,5 +1,5 @@ import "jasmine"; -import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World"; +import {GameRoom, ConnectCallback, DisconnectCallback } from "../src/Model/GameRoom"; import {Point} from "../src/Model/Websocket/MessageUserPosition"; import { Group } from "../src/Model/Group"; import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; @@ -21,7 +21,7 @@ describe("World", () => { } - const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); + const world = new GameRoom(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); world.join(createMockUser(1), new Point(100, 100)); @@ -48,7 +48,7 @@ describe("World", () => { } - const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); + const world = new GameRoom(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); world.join(createMockUser(1), new Point(100, 100)); @@ -77,7 +77,7 @@ describe("World", () => { disconnectCallNumber++; } - const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); + const world = new GameRoom(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); world.join(createMockUser(1), new Point(100, 100)); diff --git a/front/package.json b/front/package.json index 4892eba1..76e71f35 100644 --- a/front/package.json +++ b/front/package.json @@ -27,7 +27,7 @@ "google-protobuf": "^3.13.0", "phaser": "^3.22.0", "queue-typescript": "^1.0.1", - "quill": "1.3.7", + "quill": "^1.3.7", "simple-peer": "^9.6.2", "socket.io-client": "^2.3.0", "webpack-require-http": "^0.4.3" diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 59a54510..91a42882 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -1,6 +1,7 @@ import Axios from "axios"; import {API_URL} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "./RoomConnection"; +import {PositionInterface, ViewportInterface} from "./ConnexionModels"; interface LoginApiData { authToken: string @@ -16,15 +17,26 @@ class ConnectionManager { private authToken:string|null = null; private userUuid: string|null = null; + //todo: get map infos from url in anonym case public async init(): Promise { + let organizationMemberToken = null; + let teamSlug = null; + let mapSlug = null; const match = /\/register\/(.+)/.exec(window.location.toString()); - const organizationMemberToken = match ? match[1] : null; - this.initPromise = Axios.post(`${API_URL}/login`, {organizationMemberToken}).then(res => res.data); + if (match) { + organizationMemberToken = match[1]; + } else { + const match = /\/_\/(.+)\/(.+)/.exec(window.location.toString()); + teamSlug = match ? match[1] : null; + mapSlug = match ? match[2] : null; + } + this.initPromise = Axios.post(`${API_URL}/login`, {organizationMemberToken, teamSlug, mapSlug}).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; + console.log('u', this.userUuid) if (newUrl) { history.pushState({}, '', newUrl); @@ -35,9 +47,9 @@ class ConnectionManager { this.authToken = 'test'; } - public connectToRoomSocket(): Promise { + public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise { return new Promise((resolve, reject) => { - const connection = new RoomConnection(this.authToken as string); + const connection = new RoomConnection(this.authToken, roomId, name, characterLayers, position, viewport); connection.onConnectError((error: object) => { console.log('An error occurred while connecting to socket server. Retrying'); reject(error); @@ -50,7 +62,7 @@ class ConnectionManager { return new Promise((resolve, reject) => { setTimeout(() => { //todo: allow a way to break recurrsion? - this.connectToRoomSocket().then((connection) => resolve(connection)); + 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 4ec76198..3df32331 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -6,6 +6,7 @@ export enum EventMessage{ 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 JOIN_ROOM = "join-room", // bi-directional USER_POSITION = "user-position", // From client to server USER_MOVED = "user-moved", // From server to client diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index a27bd323..ed669fed 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -6,7 +6,7 @@ import { GroupDeleteMessage, GroupUpdateMessage, ItemEventMessage, - JoinRoomMessage, PlayGlobalMessage, + PlayGlobalMessage, PositionMessage, RoomJoinedMessage, ServerToClientMessage, @@ -30,7 +30,7 @@ import {ProtobufClientUtils} from "../Network/ProtobufClientUtils"; import { EventMessage, GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, - MessageUserJoined, PlayGlobalMessageInterface, + MessageUserJoined, PlayGlobalMessageInterface, PositionInterface, RoomJoinedMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface, WebRtcSignalReceivedMessageInterface, @@ -49,9 +49,25 @@ export class RoomConnection implements RoomConnection { RoomConnection.websocketFactory = websocketFactory; } - public constructor(token: string) { + /** + * + * @param token A JWT token containing the UUID of the user + * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" + */ + public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) { let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://'); - url += '?token='+token; + url += '/room/'+roomId + url += '?token='+(token ?encodeURIComponent(token):''); + url += '&name='+encodeURIComponent(name); + for (const layer of characterLayers) { + url += '&characterLayers='+encodeURIComponent(layer); + } + url += '&x='+Math.floor(position.x); + url += '&y='+Math.floor(position.y); + url += '&top='+Math.floor(viewport.top); + url += '&bottom='+Math.floor(viewport.bottom); + url += '&left='+Math.floor(viewport.left); + url += '&right='+Math.floor(viewport.right); if (RoomConnection.websocketFactory) { this.socket = RoomConnection.websocketFactory(url); @@ -107,11 +123,11 @@ export class RoomConnection implements RoomConnection { items[item.getItemid()] = JSON.parse(item.getStatejson()); } - this.resolveJoinRoom({ + this.dispatch(EventMessage.START_ROOM, { users, groups, items - }) + }); } else if (message.hasSetuseridmessage()) { this.userId = (message.getSetuseridmessage() as SetUserIdMessage).getUserid(); } else if (message.hasErrormessage()) { @@ -161,29 +177,6 @@ export class RoomConnection implements RoomConnection { this.closed = true; } - private resolveJoinRoom!: (value?: (RoomJoinedMessageInterface | PromiseLike | undefined)) => void; - - public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean, viewport: ViewportInterface): Promise { - const promise = new Promise((resolve, reject) => { - this.resolveJoinRoom = resolve; - - const positionMessage = this.toPositionMessage(startX, startY, direction, moving); - const viewportMessage = this.toViewportMessage(viewport); - - const joinRoomMessage = new JoinRoomMessage(); - joinRoomMessage.setRoomid(roomId); - joinRoomMessage.setPosition(positionMessage); - joinRoomMessage.setViewport(viewportMessage); - - //console.log('Sending position ', positionMessage.getX(), positionMessage.getY()); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setJoinroommessage(joinRoomMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); - }) - return promise; - } - private toPositionMessage(x : number, y : number, direction : string, moving: boolean): PositionMessage { const positionMessage = new PositionMessage(); positionMessage.setX(Math.floor(x)); @@ -339,6 +332,13 @@ export class RoomConnection implements RoomConnection { this.socket.addEventListener('open', callback) } + /** + * Triggered when we receive all the details of a room (users, groups, ...) + */ + public onStartRoom(callback: (event: RoomJoinedMessageInterface) => void): void { + this.onMessage(EventMessage.START_ROOM, callback); + } + public sendWebrtcSignal(signal: unknown, receiverId: number) { const webRtcSignal = new WebRtcSignalToServerMessage(); webRtcSignal.setReceiverid(receiverId); diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 960ce7e2..5188d2fe 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,4 +1,4 @@ -import {GameScene} from "./GameScene"; +import {GameScene, GameSceneInitInterface} from "./GameScene"; import { StartMapInterface } from "../../Connexion/ConnexionModels"; @@ -13,6 +13,11 @@ export interface HasMovedEvent { y: number; } +export interface loadMapResponseInterface { + key: string, + startLayerName: string; +} + export class GameManager { private playerName!: string; private characterLayers!: string[]; @@ -29,15 +34,6 @@ export class GameManager { this.characterLayers = layers; } - loadStartMap() : Promise { - return connectionManager.getMapUrlStart().then(mapUrlStart => { - return { - mapUrlStart: mapUrlStart, - startInstance: "global", //todo: is this property still usefull? - } - }); - } - getPlayerName(): string { return this.playerName; } @@ -46,8 +42,47 @@ export class GameManager { return this.characterLayers; } - loadMap(mapUrl: string, scene: Phaser.Scenes.ScenePlugin, instance: string): string { - const sceneKey = GameScene.getMapKeyByUrl(mapUrl); + /** + * Returns the map URL and the instance from the current URL + */ + private findMapUrl(): [string, string]|null { + const path = window.location.pathname; + if (!path.startsWith('/_/')) { + return null; + } + const instanceAndMap = path.substr(3); + const firstSlash = instanceAndMap.indexOf('/'); + if (firstSlash === -1) { + return null; + } + const instance = instanceAndMap.substr(0, firstSlash); + return [window.location.protocol+'//'+instanceAndMap.substr(firstSlash+1), instance]; + } + + public loadStartingMap(scene: Phaser.Scenes.ScenePlugin): Promise { + // Do we have a start URL in the address bar? If so, let's redirect to this address + const instanceAndMapUrl = this.findMapUrl(); + if (instanceAndMapUrl !== null) { + const [mapUrl, instance] = instanceAndMapUrl; + const key = gameManager.loadMap(mapUrl, scene, instance); + const startLayerName = window.location.hash ? window.location.hash.substr(1) : ''; + return Promise.resolve({key, startLayerName}); + + } else { + // If we do not have a map address in the URL, let's ask the server for a start map. + return connectionManager.getMapUrlStart().then((mapUrlStart: string) => { + const key = gameManager.loadMap(window.location.protocol + "//" + mapUrlStart, scene, 'global'); + return {key, startLayerName: ''} + }).catch((err) => { + console.error(err); + throw err; + }); + } + + } + + public loadMap(mapUrl: string, scene: Phaser.Scenes.ScenePlugin, instance: string): string { + const sceneKey = this.getMapKeyByUrl(mapUrl); const gameIndex = scene.getIndex(sceneKey); if(gameIndex === -1){ @@ -56,6 +91,13 @@ export class GameManager { } return sceneKey; } + + public getMapKeyByUrl(mapUrlStart: string) : string { + // FIXME: the key should be computed from the full URL of the map. + const startPos = mapUrlStart.indexOf('://')+3; + const endPos = mapUrlStart.indexOf(".json"); + return mapUrlStart.substring(startPos, endPos); + } } export const gameManager = new GameManager(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index f3381d5f..ba1d75fa 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -108,7 +108,6 @@ export class GameScene extends ResizableScene implements CenterListener { private simplePeer!: SimplePeer; private GlobalMessageManager!: GlobalMessageManager; private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager; - 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) @@ -139,17 +138,17 @@ export class GameScene extends ResizableScene implements CenterListener { private outlinedItem: ActionableItem|null = null; private userInputManager!: UserInputManager; - static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { - const mapKey = GameScene.getMapKeyByUrl(mapUrlFile); - if (key === null) { - key = mapKey; + static createFromUrl(mapUrlFile: string, instance: string, gameSceneKey: string|null = null): GameScene { + const mapKey = gameManager.getMapKeyByUrl(mapUrlFile); + if (gameSceneKey === null) { + gameSceneKey = mapKey; } - return new GameScene(mapKey, mapUrlFile, instance, key); + return new GameScene(mapKey, mapUrlFile, instance, gameSceneKey); } - constructor(MapKey : string, MapUrlFile: string, instance: string, key: string) { + constructor(MapKey : string, MapUrlFile: string, instance: string, gameSceneKey: string) { super({ - key: key + key: gameSceneKey }); this.GameManager = gameManager; @@ -206,105 +205,6 @@ export class GameScene extends ResizableScene implements CenterListener { loadAllLayers(this.load); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); - - this.connectionPromise = connectionManager.connectToRoomSocket().then((connection : RoomConnection) => { - this.connection = connection; - - this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected()) - - connection.onUserJoins((message: MessageUserJoined) => { - const userMessage: AddPlayerInterface = { - userId: message.userId, - characterLayers: message.characterLayers, - name: message.name, - position: message.position - } - this.addPlayer(userMessage); - }); - - connection.onUserMoved((message: UserMovedMessage) => { - const position = message.getPosition(); - if (position === undefined) { - throw new Error('Position missing from UserMovedMessage'); - } - //console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid()); - - const messageUserMoved: MessageUserMovedInterface = { - userId: message.getUserid(), - position: ProtobufClientUtils.toPointInterface(position) - } - - this.updatePlayerPosition(messageUserMoved); - }); - - connection.onUserLeft((userId: number) => { - this.removePlayer(userId); - }); - - connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { - this.shareGroupPosition(groupPositionMessage); - }) - - connection.onGroupDeleted((groupId: number) => { - try { - this.deleteGroup(groupId); - } catch (e) { - console.error(e); - } - }) - - connection.onServerDisconnected(() => { - console.log('Player disconnected from server. Reloading scene.'); - - this.simplePeer.closeAllConnections(); - this.simplePeer.unregister(); - - const key = 'somekey'+Math.round(Math.random()*10000); - const game : Phaser.Scene = GameScene.createFromUrl(this.MapUrlFile, this.instance, key); - this.scene.add(key, game, true, - { - initPosition: { - x: this.CurrentPlayer.x, - y: this.CurrentPlayer.y - } - }); - - this.scene.stop(this.scene.key); - this.scene.remove(this.scene.key); - }) - - 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.'); - return; - } - item.fire(message.event, message.state, message.parameters); - })); - - // When connection is performed, let's connect SimplePeer - this.simplePeer = new SimplePeer(this.connection); - this.GlobalMessageManager = new GlobalMessageManager(this.connection); - - const self = this; - this.simplePeer.registerPeerConnectionListener({ - onConnect(user: UserSimplePeerInterface) { - self.presentationModeSprite.setVisible(true); - self.chatModeSprite.setVisible(true); - }, - onDisconnect(userId: number) { - if (self.simplePeer.getNbConnections() === 0) { - self.presentationModeSprite.setVisible(false); - self.chatModeSprite.setVisible(false); - } - } - }) - - this.scene.wake(); - this.scene.sleep(ReconnectingSceneName); - - return connection; - }); } // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. @@ -614,6 +514,132 @@ export class GameScene extends ResizableScene implements CenterListener { this.connection.setSilent(true); } }); + + const camera = this.cameras.main; + + connectionManager.connectToRoomSocket( + this.RoomId, + gameManager.getPlayerName(), + gameManager.getCharacterSelected(), + { + x: this.startX, + y: this.startY + }, + { + left: camera.scrollX, + top: camera.scrollY, + right: camera.scrollX + camera.width, + bottom: camera.scrollY + camera.height, + }).then((connection : RoomConnection) => { + this.connection = connection; + + //this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected()) + connection.onStartRoom((roomJoinedMessage: RoomJoinedMessageInterface) => { + this.initUsersPosition(roomJoinedMessage.users); + this.connectionAnswerPromiseResolve(roomJoinedMessage); + }); + + connection.onUserJoins((message: MessageUserJoined) => { + const userMessage: AddPlayerInterface = { + userId: message.userId, + characterLayers: message.characterLayers, + name: message.name, + position: message.position + } + this.addPlayer(userMessage); + }); + + connection.onUserMoved((message: UserMovedMessage) => { + const position = message.getPosition(); + if (position === undefined) { + throw new Error('Position missing from UserMovedMessage'); + } + //console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid()); + + const messageUserMoved: MessageUserMovedInterface = { + userId: message.getUserid(), + position: ProtobufClientUtils.toPointInterface(position) + } + + this.updatePlayerPosition(messageUserMoved); + }); + + connection.onUserLeft((userId: number) => { + this.removePlayer(userId); + }); + + connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { + this.shareGroupPosition(groupPositionMessage); + }) + + connection.onGroupDeleted((groupId: number) => { + try { + this.deleteGroup(groupId); + } catch (e) { + console.error(e); + } + }) + + connection.onServerDisconnected(() => { + console.log('Player disconnected from server. Reloading scene.'); + + this.simplePeer.closeAllConnections(); + this.simplePeer.unregister(); + + const gameSceneKey = 'somekey'+Math.round(Math.random()*10000); + const game : Phaser.Scene = GameScene.createFromUrl(this.MapUrlFile, this.instance, gameSceneKey); + this.scene.add(gameSceneKey, game, true, + { + initPosition: { + x: this.CurrentPlayer.x, + y: this.CurrentPlayer.y + } + }); + + this.scene.stop(this.scene.key); + this.scene.remove(this.scene.key); + }) + + 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.'); + return; + } + item.fire(message.event, message.state, message.parameters); + })); + + // When connection is performed, let's connect SimplePeer + this.simplePeer = new SimplePeer(this.connection); + this.GlobalMessageManager = new GlobalMessageManager(this.connection); + + const self = this; + this.simplePeer.registerPeerConnectionListener({ + onConnect(user: UserSimplePeerInterface) { + self.presentationModeSprite.setVisible(true); + self.chatModeSprite.setVisible(true); + }, + onDisconnect(userId: number) { + if (self.simplePeer.getNbConnections() === 0) { + self.presentationModeSprite.setVisible(false); + self.chatModeSprite.setVisible(false); + } + } + }) + + //listen event to share position of user + this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) + this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)) + this.CurrentPlayer.on(hasMovedEventName, (event: HasMovedEvent) => { + this.gameMap.setPosition(event.x, event.y); + }) + + + this.scene.wake(); + this.scene.sleep(ReconnectingSceneName); + + return connection; + }); } private switchLayoutMode(): void { @@ -784,32 +810,6 @@ export class GameScene extends ResizableScene implements CenterListener { //create collision this.createCollisionWithPlayer(); this.createCollisionObject(); - - //join room - this.connectionPromise.then((connection: RoomConnection) => { - const camera = this.cameras.main; - connection.joinARoom(this.RoomId, - this.startX, - this.startY, - PlayerAnimationNames.WalkDown, - false, { - left: camera.scrollX, - top: camera.scrollY, - right: camera.scrollX + camera.width, - bottom: camera.scrollY + camera.height, - }).then((roomJoinedMessage: RoomJoinedMessageInterface) => { - this.initUsersPosition(roomJoinedMessage.users); - this.connectionAnswerPromiseResolve(roomJoinedMessage); - }); - // FIXME: weirdly enough we don't use the result of joinARoom !!!!!! - - //listen event to share position of user - this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) - this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)) - this.CurrentPlayer.on(hasMovedEventName, (event: HasMovedEvent) => { - this.gameMap.setPosition(event.x, event.y); - }) - }); } pushPlayerPosition(event: HasMovedEvent) { @@ -979,7 +979,6 @@ export class GameScene extends ResizableScene implements CenterListener { type: "InitUserPositionEvent", event: usersPosition }); - } /** @@ -1133,12 +1132,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.groups.delete(groupId); } - public static getMapKeyByUrl(mapUrlStart: string) : string { - // FIXME: the key should be computed from the full URL of the map. - const startPos = mapUrlStart.indexOf('://')+3; - const endPos = mapUrlStart.indexOf(".json"); - return mapUrlStart.substring(startPos, endPos); - } + /** * Sends to the server an event emitted by one of the ActionableItems. diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 6ac1ad47..5d5339d9 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -94,7 +94,7 @@ export class EnableCameraScene extends Phaser.Scene { this.add.existing(this.logo); this.input.keyboard.on('keyup-ENTER', () => { - return this.login(); + this.login(); }); this.getElementByIdOrFail('webRtcSetup').classList.add('active'); @@ -258,7 +258,7 @@ export class EnableCameraScene extends Phaser.Scene { this.soundMeterSprite.setVolume(this.soundMeter.getVolume()); } - private async login(): Promise { + private async login(): Promise { this.getElementByIdOrFail('webRtcSetup').style.display = 'none'; this.soundMeter.stop(); window.removeEventListener('resize', this.repositionCallback); @@ -266,46 +266,8 @@ export class EnableCameraScene extends Phaser.Scene { mediaManager.stopCamera(); mediaManager.stopMicrophone(); - // Do we have a start URL in the address bar? If so, let's redirect to this address - const instanceAndMapUrl = this.findMapUrl(); - if (instanceAndMapUrl !== null) { - const [mapUrl, instance] = instanceAndMapUrl; - const key = gameManager.loadMap(mapUrl, this.scene, instance); - this.scene.start(key, { - startLayerName: window.location.hash ? window.location.hash.substr(1) : undefined - } as GameSceneInitInterface); - return { - mapUrlStart: mapUrl, - startInstance: instance - }; - } else { - // If we do not have a map address in the URL, let's ask the server for a start map. - return gameManager.loadStartMap().then((startMap: StartMapInterface) => { - const key = gameManager.loadMap(window.location.protocol + "//" + startMap.mapUrlStart, this.scene, startMap.startInstance); - this.scene.start(key); - return startMap; - }).catch((err) => { - console.error(err); - throw err; - }); - } - } - - /** - * Returns the map URL and the instance from the current URL - */ - private findMapUrl(): [string, string]|null { - const path = window.location.pathname; - if (!path.startsWith('/_/')) { - return null; - } - const instanceAndMap = path.substr(3); - const firstSlash = instanceAndMap.indexOf('/'); - if (firstSlash === -1) { - return null; - } - const instance = instanceAndMap.substr(0, firstSlash); - return [window.location.protocol+'//'+instanceAndMap.substr(firstSlash+1), instance]; + const {key, startLayerName} = await gameManager.loadStartingMap(this.scene); + this.scene.start(key, {startLayerName}); } private async getDevices() { diff --git a/front/yarn.lock b/front/yarn.lock index 933a02b4..5d235a82 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -3871,7 +3871,7 @@ quill-delta@^3.6.2: extend "^3.0.2" fast-diff "1.1.2" -quill@1.3.7: +quill@^1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== diff --git a/messages/messages.proto b/messages/messages.proto index 87cf2231..63c3ddaf 100644 --- a/messages/messages.proto +++ b/messages/messages.proto @@ -38,12 +38,6 @@ message SetPlayerDetailsMessage { repeated string characterLayers = 2; } -message JoinRoomMessage { - string roomId = 1; - PositionMessage position = 2; - ViewportMessage viewport = 3; -} - message UserMovesMessage { PositionMessage position = 1; ViewportMessage viewport = 2; @@ -56,7 +50,6 @@ message WebRtcSignalToServerMessage { message ClientToServerMessage { oneof message { - JoinRoomMessage joinRoomMessage = 1; UserMovesMessage userMovesMessage = 2; SilentMessage silentMessage = 3; ViewportMessage viewportMessage = 4;