Merge branch 'develop' into player-report

# Conflicts:
#	back/src/Controller/IoSocketController.ts
#	front/src/Phaser/Game/GameScene.ts
#	front/src/index.ts
#	messages/messages.proto
This commit is contained in:
Gregoire Parant 2020-10-13 20:39:29 +02:00
commit 05a1ea8469
28 changed files with 1189 additions and 975 deletions

View file

@ -1,43 +1,78 @@
import Axios from "axios";
import {API_URL} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "./RoomConnection";
import {PositionInterface, ViewportInterface} from "./ConnexionModels";
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
import {localUserStore} from "./LocalUserStore";
import {LocalUser} from "./LocalUser";
import {Room} from "./Room";
interface LoginApiData {
authToken: string
userUuid: string
mapUrlStart: string
newUrl: string
}
const URL_ROOM_STARTED = '/Floor0/floor0.json';
class ConnectionManager {
private initPromise!: Promise<LoginApiData>;
private mapUrlStart: string|null = null;
private localUser!:LocalUser;
private authToken:string|null = null;
private userUuid: string|null = null;
/**
* Tries to login to the node server and return the starting map url to be loaded
*/
public async initGameConnexion(): Promise<Room> {
public async init(): Promise<void> {
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);
const data = await this.initPromise
this.authToken = data.authToken;
this.userUuid = data.userUuid;
this.mapUrlStart = data.mapUrlStart;
const newUrl = data.newUrl;
const connexionType = urlManager.getGameConnexionType();
if(connexionType === GameConnexionTypes.register) {
const organizationMemberToken = urlManager.getOrganizationToken();
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);
if (newUrl) {
history.pushState({}, '', newUrl);
const organizationSlug = data.organizationSlug;
const worldSlug = data.worldSlug;
const roomSlug = data.roomSlug;
urlManager.editUrlForRoom(roomSlug, organizationSlug, worldSlug);
const room = new Room(window.location.pathname);
return Promise.resolve(room);
} else if (connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
const localUser = localUserStore.getLocalUser();
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);
}
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();
if (localUser) {
this.localUser = localUser
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');
}
}
return Promise.reject('Invalid URL');
}
public initBenchmark(): void {
this.authToken = 'test';
this.localUser = new LocalUser('', 'test');
}
public connectToRoomSocket(): Promise<RoomConnection> {
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise<RoomConnection> {
return new Promise<RoomConnection>((resolve, reject) => {
const connection = new RoomConnection(this.authToken as string);
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
connection.onConnectError((error: object) => {
console.log('An error occurred while connecting to socket server. Retrying');
reject(error);
@ -50,20 +85,11 @@ class ConnectionManager {
return new Promise<RoomConnection>((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) );
});
});
}
public getMapUrlStart(): Promise<string> {
return this.initPromise.then(() => {
if (!this.mapUrlStart) {
throw new Error('No map url set!');
}
return this.mapUrlStart;
})
}
}
export const connectionManager = new ConnectionManager();

View file

@ -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

View file

@ -0,0 +1,9 @@
export class LocalUser {
public uuid: string;
public jwtToken: string;
constructor(uuid:string, jwtToken: string) {
this.uuid = uuid;
this.jwtToken = jwtToken;
}
}

View file

@ -0,0 +1,16 @@
import {LocalUser} from "./LocalUser";
class LocalUserStore {
saveUser(localUser: LocalUser) {
localStorage.setItem('localUser', JSON.stringify(localUser));
}
getLocalUser(): LocalUser|null {
const data = localStorage.getItem('localUser');
return data ? JSON.parse(data) : null;
}
}
export const localUserStore = new LocalUserStore();

View file

@ -0,0 +1,91 @@
import Axios from "axios";
import {API_URL} from "../Enum/EnvironmentVariable";
export class Room {
public readonly id: string;
public readonly isPublic: boolean;
private mapUrl: string|undefined;
private instance: string|undefined;
constructor(id: 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');
}
}
public async getMapUrl(): Promise<string> {
return new Promise<string>((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);
Axios.get(`${API_URL}/map`, {
params: urlParts
}).then(({data}) => {
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
resolve(data.mapUrl);
return;
});
}
});
}
/**
* Instance name is:
* - In a public URL: the second part of the URL ( _/[instance]/map.json)
* - In a private URL: [organizationId/worldId]
*/
public getInstance(): string {
if (this.instance !== undefined) {
return this.instance;
}
if (this.isPublic) {
const match = /_\/([^/]+)\/.+/.exec(this.id);
if (!match) throw new Error('Could not extract instance from "'+this.id+'"');
this.instance = match[1];
return this.instance;
} else {
const match = /_\/([^/]+)\/([^/]+)\/.+/.exec(this.id);
if (!match) throw new Error('Could not extract instance from "'+this.id+'"');
this.instance = match[1]+'/'+match[2];
return this.instance;
}
}
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);
}
const results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
organizationSlug: match[1],
worldSlug: match[2],
}
if (match[3] !== undefined) {
results.roomSlug = match[3];
}
return results;
}
}

View file

@ -6,12 +6,11 @@ import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
JoinRoomMessage, PlayGlobalMessage,
PlayGlobalMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
SetPlayerDetailsMessage,
SetUserIdMessage,
SilentMessage, StopGlobalMessage,
UserJoinedMessage,
UserLeftMessage,
@ -32,7 +31,7 @@ import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
import {
EventMessage,
GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface,
MessageUserJoined, PlayGlobalMessageInterface,
MessageUserJoined, PlayGlobalMessageInterface, PositionInterface,
RoomJoinedMessageInterface,
ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface,
@ -51,9 +50,26 @@ 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';
url += '?roomId='+(roomId ?encodeURIComponent(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);
@ -109,13 +125,13 @@ export class RoomConnection implements RoomConnection {
items[item.getItemid()] = JSON.parse(item.getStatejson());
}
this.resolveJoinRoom({
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()) {
@ -165,29 +181,6 @@ export class RoomConnection implements RoomConnection {
this.closed = true;
}
private resolveJoinRoom!: (value?: (RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface> | undefined)) => void;
public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean, viewport: ViewportInterface): Promise<RoomJoinedMessageInterface> {
const promise = new Promise<RoomJoinedMessageInterface>((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));
@ -343,6 +336,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);