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:
commit
05a1ea8469
28 changed files with 1189 additions and 975 deletions
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
9
front/src/Connexion/LocalUser.ts
Normal file
9
front/src/Connexion/LocalUser.ts
Normal 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;
|
||||
}
|
||||
}
|
16
front/src/Connexion/LocalUserStore.ts
Normal file
16
front/src/Connexion/LocalUserStore.ts
Normal 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();
|
91
front/src/Connexion/Room.ts
Normal file
91
front/src/Connexion/Room.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue