Merge branch 'develop' into GlobalMessageToWorld
# Conflicts: # CHANGELOG.md # front/src/Api/Events/IframeEvent.ts # front/src/Components/App.svelte # pusher/src/Services/SocketManager.ts
This commit is contained in:
commit
14b4229019
48 changed files with 1346 additions and 1634 deletions
4
front/dist/resources/html/gameMenu.html
vendored
4
front/dist/resources/html/gameMenu.html
vendored
|
@ -60,6 +60,10 @@
|
|||
<section>
|
||||
<button id="enableNotification">Enable notifications</button>
|
||||
</section>
|
||||
<!-- TODO activate authentication -->
|
||||
<section hidden>
|
||||
<button id="oidcLogin">Oauth Login</button>
|
||||
</section>
|
||||
<section>
|
||||
<button id="sparkButton">Create map</button>
|
||||
</section>
|
||||
|
|
18
front/dist/resources/html/warningContainer.html
vendored
18
front/dist/resources/html/warningContainer.html
vendored
|
@ -1,18 +0,0 @@
|
|||
<style>
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
width: 300px;
|
||||
background-color: red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#warningMain h2 {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main id="warningMain">
|
||||
<h2>Warning!</h2>
|
||||
<p>This world is close to its limit!</p>
|
||||
</main>
|
|
@ -10,6 +10,7 @@
|
|||
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||
"@types/node": "^15.3.0",
|
||||
"@types/quill": "^1.3.7",
|
||||
"@types/uuidv4": "^5.0.0",
|
||||
"@types/webpack-dev-server": "^3.11.4",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
|
@ -54,7 +55,8 @@
|
|||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.11.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"standardized-audio-context": "^25.2.4"
|
||||
"standardized-audio-context": "^25.2.4",
|
||||
"uuidv4": "^6.2.10"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "run-p templater serve svelte-check-watch",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as tg from "generic-type-guard";
|
||||
import type { GameStateEvent } from "./GameStateEvent";
|
||||
import type { ButtonClickedEvent } from "./ButtonClickedEvent";
|
||||
import type { ChatEvent } from "./ChatEvent";
|
||||
import type { ClosePopupEvent } from "./ClosePopupEvent";
|
||||
|
@ -10,7 +9,6 @@ import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent";
|
|||
import type { OpenPopupEvent } from "./OpenPopupEvent";
|
||||
import type { OpenTabEvent } from "./OpenTabEvent";
|
||||
import type { UserInputChatEvent } from "./UserInputChatEvent";
|
||||
import type { MapDataEvent } from "./MapDataEvent";
|
||||
import type { LayerEvent } from "./LayerEvent";
|
||||
import type { SetPropertyEvent } from "./setPropertyEvent";
|
||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||
|
@ -23,6 +21,8 @@ import type { SetVariableEvent } from "./SetVariableEvent";
|
|||
import { isGameStateEvent } from "./GameStateEvent";
|
||||
import { isMapDataEvent } from "./MapDataEvent";
|
||||
import { isSetVariableEvent } from "./SetVariableEvent";
|
||||
import type { LoadTilesetEvent } from "./LoadTilesetEvent";
|
||||
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T;
|
||||
|
@ -52,6 +52,7 @@ export type IframeEventMap = {
|
|||
playSound: PlaySoundEvent;
|
||||
stopSound: null;
|
||||
getState: undefined;
|
||||
loadTileset: LoadTilesetEvent;
|
||||
registerMenuCommand: MenuItemRegisterEvent;
|
||||
setTiles: SetTilesEvent;
|
||||
};
|
||||
|
@ -100,6 +101,10 @@ export const iframeQueryMapTypeGuards = {
|
|||
query: isSetVariableEvent,
|
||||
answer: tg.isUndefined,
|
||||
},
|
||||
loadTileset: {
|
||||
query: isLoadTilesetEvent,
|
||||
answer: tg.isNumber,
|
||||
},
|
||||
};
|
||||
|
||||
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||
|
|
12
front/src/Api/Events/LoadTilesetEvent.ts
Normal file
12
front/src/Api/Events/LoadTilesetEvent.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isLoadTilesetEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
url: tg.isString,
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type LoadTilesetEvent = tg.GuardedType<typeof isLoadTilesetEvent>;
|
|
@ -26,9 +26,6 @@ import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
|||
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
||||
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
|
||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||
import { isMenuItemRegisterEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||
import type { MapDataEvent } from "./Events/MapDataEvent";
|
||||
import type { GameStateEvent } from "./Events/GameStateEvent";
|
||||
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Observable, Subject } from "rxjs";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
||||
|
||||
|
@ -105,6 +105,14 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
|||
}
|
||||
return mapURL;
|
||||
}
|
||||
async loadTileset(url: string): Promise<number> {
|
||||
return await queryWorkadventure({
|
||||
type: "loadTileset",
|
||||
data: {
|
||||
url: url,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkadventureRoomCommands();
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
import TextMessage from "./TypeMessage/TextMessage.svelte";
|
||||
import {banMessageVisibleStore} from "../Stores/TypeMessageStore/BanMessageStore";
|
||||
import {textMessageVisibleStore} from "../Stores/TypeMessageStore/TextMessageStore";
|
||||
import {warningContainerStore} from "../Stores/MenuStore";
|
||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
|
@ -105,4 +107,7 @@
|
|||
{#if $chatVisibilityStore}
|
||||
<Chat></Chat>
|
||||
{/if}
|
||||
{#if $warningContainerStore}
|
||||
<WarningContainer></WarningContainer>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {userIsAdminStore} from "../../Stores/GameStore";
|
||||
import {ADMIN_URL} from "../../Enum/EnvironmentVariable";
|
||||
|
||||
const upgradeLink = ADMIN_URL+'/pricing';
|
||||
|
||||
</script>
|
||||
|
||||
<main class="warningMain" transition:fly="{{ y: -200, duration: 500 }}">
|
||||
<h2>Warning!</h2>
|
||||
{#if $userIsAdminStore}
|
||||
<p>This world is close to its limit!. You can upgrade its capacity <a href="{upgradeLink}" target="_blank">here</a></p>
|
||||
{:else}
|
||||
<p>This world is close to its limit!</p>
|
||||
{/if}
|
||||
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
main.warningMain {
|
||||
pointer-events: auto;
|
||||
width: 100vw;
|
||||
background-color: red;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
font-family: Lato;
|
||||
min-width: 300px;
|
||||
opacity: 0.9;
|
||||
z-index: 2;
|
||||
h2 {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -14,6 +14,7 @@ class ConnectionManager {
|
|||
private connexionType?: GameConnexionTypes;
|
||||
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
||||
private _unloading: boolean = false;
|
||||
private authToken: string | null = null;
|
||||
|
||||
private serviceWorker?: _ServiceWorker;
|
||||
|
||||
|
@ -27,21 +28,54 @@ class ConnectionManager {
|
|||
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
public loadOpenIDScreen() {
|
||||
localUserStore.setAuthToken(null);
|
||||
const state = localUserStore.generateState();
|
||||
const nonce = localUserStore.generateNonce();
|
||||
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`);
|
||||
}
|
||||
|
||||
public logout() {
|
||||
localUserStore.setAuthToken(null);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to login to the node server and return the starting map url to be loaded
|
||||
*/
|
||||
public async initGameConnexion(): Promise<Room> {
|
||||
const connexionType = urlManager.getGameConnexionType();
|
||||
this.connexionType = connexionType;
|
||||
|
||||
let room: Room | null = null;
|
||||
if (connexionType === GameConnexionTypes.register) {
|
||||
if (connexionType === GameConnexionTypes.jwt) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.authToken = authToken;
|
||||
room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
} else if (connexionType === GameConnexionTypes.register) {
|
||||
//@deprecated
|
||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
||||
this.localUser = new LocalUser(data.userUuid, data.textures);
|
||||
this.authToken = data.authToken;
|
||||
localUserStore.saveUser(this.localUser);
|
||||
localUserStore.setAuthToken(this.authToken);
|
||||
|
||||
const roomUrl = data.roomUrl;
|
||||
|
||||
|
@ -61,24 +95,12 @@ class ConnectionManager {
|
|||
connexionType === GameConnexionTypes.anonymous ||
|
||||
connexionType === GameConnexionTypes.empty
|
||||
) {
|
||||
let localUser = localUserStore.getLocalUser();
|
||||
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
|
||||
this.localUser = localUser;
|
||||
try {
|
||||
await this.verifyToken(localUser.jwtToken);
|
||||
} catch (e) {
|
||||
// If the token is invalid, let's generate an anonymous one.
|
||||
console.error("JWT token invalid. Did it expire? Login anonymously instead.");
|
||||
await this.anonymousLogin();
|
||||
}
|
||||
} else {
|
||||
this.authToken = localUserStore.getAuthToken();
|
||||
//todo: add here some kind of warning if authToken has expired.
|
||||
if (!this.authToken) {
|
||||
await this.anonymousLogin();
|
||||
}
|
||||
|
||||
localUser = localUserStore.getLocalUser();
|
||||
if (!localUser) {
|
||||
throw "Error to store local user data";
|
||||
}
|
||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||
|
||||
let roomPath: string;
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
|
@ -97,19 +119,18 @@ class ConnectionManager {
|
|||
room = await Room.createRoom(new URL(roomPath));
|
||||
if (room.textures != undefined && room.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if (localUser.textures.length === 0) {
|
||||
localUser.textures = room.textures;
|
||||
if (this.localUser.textures.length === 0) {
|
||||
this.localUser.textures = room.textures;
|
||||
} else {
|
||||
room.textures.forEach((newTexture) => {
|
||||
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
||||
if (localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
||||
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||
return;
|
||||
}
|
||||
localUser?.textures.push(newTexture);
|
||||
this.localUser.textures.push(newTexture);
|
||||
});
|
||||
}
|
||||
this.localUser = localUser;
|
||||
localUserStore.saveUser(localUser);
|
||||
localUserStore.saveUser(this.localUser);
|
||||
}
|
||||
}
|
||||
if (room == undefined) {
|
||||
|
@ -120,21 +141,19 @@ class ConnectionManager {
|
|||
return Promise.resolve(room);
|
||||
}
|
||||
|
||||
private async verifyToken(token: string): Promise<void> {
|
||||
await Axios.get(`${PUSHER_URL}/verify`, { params: { token } });
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
||||
this.localUser = new LocalUser(data.userUuid, data.authToken, []);
|
||||
this.localUser = new LocalUser(data.userUuid, []);
|
||||
this.authToken = data.authToken;
|
||||
if (!isBenchmark) {
|
||||
// In benchmark, we don't have a local storage.
|
||||
localUserStore.saveUser(this.localUser);
|
||||
localUserStore.setAuthToken(this.authToken);
|
||||
}
|
||||
}
|
||||
|
||||
public initBenchmark(): void {
|
||||
this.localUser = new LocalUser("", "test", []);
|
||||
this.localUser = new LocalUser("", []);
|
||||
}
|
||||
|
||||
public connectToRoomSocket(
|
||||
|
@ -147,7 +166,7 @@ class ConnectionManager {
|
|||
): Promise<OnConnectInterface> {
|
||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||
const connection = new RoomConnection(
|
||||
this.localUser.jwtToken,
|
||||
this.authToken,
|
||||
roomUrl,
|
||||
name,
|
||||
characterLayers,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {MAX_USERNAME_LENGTH} from "../Enum/EnvironmentVariable";
|
||||
import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable";
|
||||
|
||||
export interface CharacterTexture {
|
||||
id: number,
|
||||
level: number,
|
||||
url: string,
|
||||
rights: string
|
||||
id: number;
|
||||
level: number;
|
||||
url: string;
|
||||
rights: string;
|
||||
}
|
||||
|
||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||
|
@ -24,6 +24,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
|
|||
}
|
||||
|
||||
export class LocalUser {
|
||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
|
||||
}
|
||||
constructor(public readonly uuid: string, public textures: CharacterTexture[]) {}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const playerNameKey = "playerName";
|
||||
const selectedPlayerKey = "selectedPlayer";
|
||||
|
@ -12,6 +13,9 @@ const audioPlayerMuteKey = "audioMute";
|
|||
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
||||
const fullscreenKey = "fullscreen";
|
||||
const lastRoomUrl = "lastRoomUrl";
|
||||
const authToken = "authToken";
|
||||
const state = "state";
|
||||
const nonce = "nonce";
|
||||
|
||||
class LocalUserStore {
|
||||
saveUser(localUser: LocalUser) {
|
||||
|
@ -116,6 +120,36 @@ class LocalUserStore {
|
|||
getLastRoomUrl(): string {
|
||||
return localStorage.getItem(lastRoomUrl) ?? "";
|
||||
}
|
||||
|
||||
setAuthToken(value: string | null) {
|
||||
value ? localStorage.setItem(authToken, value) : localStorage.removeItem(authToken);
|
||||
}
|
||||
getAuthToken(): string | null {
|
||||
return localStorage.getItem(authToken);
|
||||
}
|
||||
|
||||
generateState(): string {
|
||||
const newState = uuidv4();
|
||||
localStorage.setItem(state, newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
verifyState(value: string): boolean {
|
||||
const oldValue = localStorage.getItem(state);
|
||||
localStorage.removeItem(state);
|
||||
return oldValue === value;
|
||||
}
|
||||
generateNonce(): string {
|
||||
const newNonce = uuidv4();
|
||||
localStorage.setItem(nonce, newNonce);
|
||||
return newNonce;
|
||||
}
|
||||
|
||||
getNonce(): string | null {
|
||||
const oldValue = localStorage.getItem(nonce);
|
||||
localStorage.removeItem(nonce);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
export const localUserStore = new LocalUserStore();
|
||||
|
|
|
@ -55,9 +55,9 @@ import {
|
|||
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
||||
import { adminMessagesService } from "./AdminMessagesService";
|
||||
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
||||
import { worldFullWarningStream } from "./WorldFullWarningStream";
|
||||
import { connectionManager } from "./ConnectionManager";
|
||||
import { emoteEventStream } from "./EmoteEventStream";
|
||||
import { warningContainerStore } from "../Stores/MenuStore";
|
||||
|
||||
const manualPingDelay = 20000;
|
||||
|
||||
|
@ -76,7 +76,7 @@ export class RoomConnection implements RoomConnection {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param token A JWT token containing the UUID of the user
|
||||
* @param token A JWT token containing the email of the user
|
||||
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
|
||||
*/
|
||||
public constructor(
|
||||
|
@ -217,6 +217,9 @@ export class RoomConnection implements RoomConnection {
|
|||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasTokenexpiredmessage()) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
this.closed = true;
|
||||
|
@ -244,7 +247,7 @@ export class RoomConnection implements RoomConnection {
|
|||
} else if (message.hasBanusermessage()) {
|
||||
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
|
||||
} else if (message.hasWorldfullwarningmessage()) {
|
||||
worldFullWarningStream.onMessage();
|
||||
warningContainerStore.activateWarningContainer();
|
||||
} else if (message.hasRefreshroommessage()) {
|
||||
//todo: implement a way to notify the user the room was refreshed.
|
||||
} else {
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullWarningStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullWarningStream = new WorldFullWarningStream();
|
|
@ -1,22 +1,24 @@
|
|||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
||||
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
|
||||
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
||||
const START_ROOM_URL: string =
|
||||
process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
||||
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
||||
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
||||
const TURN_USER: string = process.env.TURN_USER || '';
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||
const TURN_USER: string = process.env.TURN_USER || "";
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || "";
|
||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
const JITSI_PRIVATE_MODE: boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
|
||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
|
||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8;
|
||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
||||
|
||||
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
|
||||
export {
|
||||
DEBUG_MODE,
|
||||
|
@ -32,5 +34,5 @@ export {
|
|||
TURN_USER,
|
||||
TURN_PASSWORD,
|
||||
JITSI_URL,
|
||||
JITSI_PRIVATE_MODE
|
||||
}
|
||||
JITSI_PRIVATE_MODE,
|
||||
};
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
export const warningContainerKey = 'warningContainer';
|
||||
export const warningContainerHtml = 'resources/html/warningContainer.html';
|
||||
|
||||
export class WarningContainer extends Phaser.GameObjects.DOMElement {
|
||||
|
||||
constructor(scene: Phaser.Scene) {
|
||||
super(scene, 100, 0);
|
||||
this.setOrigin(0, 0);
|
||||
this.createFromCache(warningContainerKey);
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -74,8 +74,6 @@ import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey }
|
|||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { EmoteManager } from "./EmoteManager";
|
||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||
import RenderTexture = Phaser.GameObjects.RenderTexture;
|
||||
import Tilemap = Phaser.Tilemaps.Tilemap;
|
||||
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||
|
||||
import AnimatedTiles from "phaser-animated-tiles";
|
||||
|
@ -87,6 +85,8 @@ import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStor
|
|||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
import { playersStore } from "../../Stores/PlayersStore";
|
||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||
import Tileset = Phaser.Tilemaps.Tileset;
|
||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
|
@ -219,6 +219,9 @@ export class GameScene extends DirtyScene {
|
|||
|
||||
//hook preload scene
|
||||
preload(): void {
|
||||
//initialize frame event of scripting API
|
||||
this.listenToIframeEvents();
|
||||
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
|
@ -547,7 +550,6 @@ export class GameScene extends DirtyScene {
|
|||
);
|
||||
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
this.listenToIframeEvents();
|
||||
|
||||
if (!this.room.isDisconnected()) {
|
||||
this.connect();
|
||||
|
@ -602,6 +604,8 @@ export class GameScene extends DirtyScene {
|
|||
|
||||
playersStore.connectToRoomConnection(this.connection);
|
||||
|
||||
userIsAdminStore.set(this.connection.hasTag("admin"));
|
||||
|
||||
this.connection.onUserJoins((message: MessageUserJoined) => {
|
||||
const userMessage: AddPlayerInterface = {
|
||||
userId: message.userId,
|
||||
|
@ -1078,8 +1082,74 @@ ${escapedMessage}
|
|||
for (const eventTile of eventTiles) {
|
||||
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
||||
}
|
||||
this.markDirty();
|
||||
})
|
||||
);
|
||||
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
|
||||
return this.connectionAnswerPromise.then(() => {
|
||||
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
|
||||
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
|
||||
let newFirstgid = 1;
|
||||
const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1];
|
||||
if (lastTileset) {
|
||||
//If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset
|
||||
newFirstgid = lastTileset.firstgid + lastTileset.tilecount;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.load.on("filecomplete-json-" + eventTileset.url, () => {
|
||||
let jsonTileset = this.cache.json.get(eventTileset.url);
|
||||
const imageUrl = jsonTilesetDir + "/" + jsonTileset.image;
|
||||
this.load.image(imageUrl, imageUrl);
|
||||
this.load.on("filecomplete-image-" + imageUrl, () => {
|
||||
//Add the firstgid of the tileset to the json file
|
||||
jsonTileset = { ...jsonTileset, firstgid: newFirstgid };
|
||||
this.mapFile.tilesets.push(jsonTileset);
|
||||
this.Map.tilesets.push(
|
||||
new Tileset(
|
||||
jsonTileset.name,
|
||||
jsonTileset.firstgid,
|
||||
jsonTileset.tileWidth,
|
||||
jsonTileset.tileHeight,
|
||||
jsonTileset.margin,
|
||||
jsonTileset.spacing,
|
||||
jsonTileset.tiles
|
||||
)
|
||||
);
|
||||
this.Terrains.push(
|
||||
this.Map.addTilesetImage(
|
||||
jsonTileset.name,
|
||||
imageUrl,
|
||||
jsonTileset.tilewidth,
|
||||
jsonTileset.tileheight,
|
||||
jsonTileset.margin,
|
||||
jsonTileset.spacing
|
||||
)
|
||||
);
|
||||
//destroy the tilemapayer because they are unique and we need to reuse their key and layerdData
|
||||
for (const layer of this.Map.layers) {
|
||||
layer.tilemapLayer.destroy(false);
|
||||
}
|
||||
//Create a new GameMap with the changed file
|
||||
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
||||
//Destroy the colliders of the old tilemapLayer
|
||||
this.physics.add.world.colliders.destroy();
|
||||
//Create new colliders with the new GameMap
|
||||
this.createCollisionWithPlayer();
|
||||
//Create new trigger with the new GameMap
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
resolve(newFirstgid);
|
||||
});
|
||||
});
|
||||
this.load.on("loaderror", () => {
|
||||
console.error("Error while loading " + eventTileset.url + ".");
|
||||
reject(-1);
|
||||
});
|
||||
|
||||
this.load.json(eventTileset.url, eventTileset.url);
|
||||
this.load.start();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private setPropertyLayer(
|
||||
|
@ -1147,7 +1217,7 @@ ${escapedMessage}
|
|||
let targetRoom: Room;
|
||||
try {
|
||||
targetRoom = await Room.createRoom(roomUrl);
|
||||
} catch (e: unknown) {
|
||||
} catch (e) {
|
||||
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
||||
this.mapTransitioning = false;
|
||||
return;
|
||||
|
@ -1201,6 +1271,7 @@ ${escapedMessage}
|
|||
this.chatVisibilityUnsubscribe();
|
||||
this.biggestAvailableAreaStoreUnsubscribe();
|
||||
iframeListener.unregisterAnswerer("getState");
|
||||
iframeListener.unregisterAnswerer("loadTileset");
|
||||
this.sharedVariablesManager?.close();
|
||||
|
||||
mediaManager.hideGameOverlay();
|
||||
|
@ -1273,7 +1344,7 @@ ${escapedMessage}
|
|||
try {
|
||||
const room = await Room.createRoom(exitRoomPath);
|
||||
return gameManager.loadMap(room, this.scene);
|
||||
} catch (e: unknown) {
|
||||
} catch (e) {
|
||||
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
|||
import { gameReportKey, gameReportRessource, ReportMenu } from "./ReportMenu";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { GameConnexionTypes } from "../../Url/UrlManager";
|
||||
import { WarningContainer, warningContainerHtml, warningContainerKey } from "../Components/WarningContainer";
|
||||
import { worldFullWarningStream } from "../../Connexion/WorldFullWarningStream";
|
||||
import { menuIconVisible } from "../../Stores/MenuStore";
|
||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||
|
@ -21,6 +19,7 @@ import { get } from "svelte/store";
|
|||
import { playersStore } from "../../Stores/PlayersStore";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
export const MenuSceneName = "MenuScene";
|
||||
const gameMenuKey = "gameMenu";
|
||||
|
@ -45,8 +44,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
private gameQualityValue: number;
|
||||
private videoQualityValue: number;
|
||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
||||
private warningContainer: WarningContainer | null = null;
|
||||
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
||||
private subscriptions = new Subscription();
|
||||
constructor() {
|
||||
super({ key: MenuSceneName });
|
||||
|
@ -91,7 +88,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.load.html(gameSettingsMenuKey, "resources/html/gameQualityMenu.html");
|
||||
this.load.html(gameShare, "resources/html/gameShare.html");
|
||||
this.load.html(gameReportKey, gameReportRessource);
|
||||
this.load.html(warningContainerKey, warningContainerHtml);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
@ -147,7 +143,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.menuElement.addListener("click");
|
||||
this.menuElement.on("click", this.onMenuClick.bind(this));
|
||||
|
||||
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
|
||||
chatVisibilityStore.subscribe((v) => {
|
||||
this.menuButton.setVisible(!v);
|
||||
});
|
||||
|
@ -194,20 +189,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
private showWorldCapacityWarning() {
|
||||
if (!this.warningContainer) {
|
||||
this.warningContainer = new WarningContainer(this);
|
||||
}
|
||||
if (this.warningContainerTimeout) {
|
||||
clearTimeout(this.warningContainerTimeout);
|
||||
}
|
||||
this.warningContainerTimeout = setTimeout(() => {
|
||||
this.warningContainer?.destroy();
|
||||
this.warningContainer = null;
|
||||
this.warningContainerTimeout = null;
|
||||
}, 120000);
|
||||
}
|
||||
|
||||
private closeSideMenu(): void {
|
||||
if (!this.sideMenuOpened) return;
|
||||
this.sideMenuOpened = false;
|
||||
|
@ -363,6 +344,9 @@ export class MenuScene extends Phaser.Scene {
|
|||
case "editGameSettingsButton":
|
||||
this.openGameSettingsMenu();
|
||||
break;
|
||||
case "oidcLogin":
|
||||
connectionManager.loadOpenIDScreen();
|
||||
break;
|
||||
case "toggleFullscreen":
|
||||
this.toggleFullscreen();
|
||||
break;
|
||||
|
@ -403,6 +387,10 @@ export class MenuScene extends Phaser.Scene {
|
|||
private gotToCreateMapPage() {
|
||||
//const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
|
||||
//TODO fix me: this button can to send us on WorkAdventure BO.
|
||||
//const sparkHost = ADMIN_URL + "/getting-started";
|
||||
|
||||
//The redirection must be only on workadventu.re domain
|
||||
//To day the domain staging cannot be use by customer
|
||||
const sparkHost = "https://workadventu.re/getting-started";
|
||||
window.open(sparkHost, "_blank");
|
||||
}
|
||||
|
|
|
@ -2,4 +2,6 @@ import { writable } from "svelte/store";
|
|||
|
||||
export const userMovingStore = writable(false);
|
||||
|
||||
export const requestVisitCardsStore = writable<string|null>(null);
|
||||
export const requestVisitCardsStore = writable<string | null>(null);
|
||||
|
||||
export const userIsAdminStore = writable(false);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
import { derived, writable, Writable } from "svelte/store";
|
||||
import { writable } from "svelte/store";
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export const menuIconVisible = writable(false);
|
||||
|
||||
let warningContainerTimeout: Timeout | null = null;
|
||||
function createWarningContainerStore() {
|
||||
const { subscribe, set } = writable<boolean>(false);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
activateWarningContainer() {
|
||||
set(true);
|
||||
if (warningContainerTimeout) clearTimeout(warningContainerTimeout);
|
||||
warningContainerTimeout = setTimeout(() => {
|
||||
set(false);
|
||||
warningContainerTimeout = null;
|
||||
}, 120000);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const warningContainerStore = createWarningContainerStore();
|
||||
|
|
|
@ -1,45 +1,46 @@
|
|||
import type {Room} from "../Connexion/Room";
|
||||
import type { Room } from "../Connexion/Room";
|
||||
|
||||
export enum GameConnexionTypes {
|
||||
anonymous=1,
|
||||
anonymous = 1,
|
||||
organization,
|
||||
register,
|
||||
empty,
|
||||
unknown,
|
||||
jwt,
|
||||
}
|
||||
|
||||
//this class is responsible with analysing and editing the game's url
|
||||
class UrlManager {
|
||||
|
||||
//todo: use that to detect if we can find a token in localstorage
|
||||
public getGameConnexionType(): GameConnexionTypes {
|
||||
const url = window.location.pathname.toString();
|
||||
if (url.includes('_/')) {
|
||||
if (url === "/jwt") {
|
||||
return GameConnexionTypes.jwt;
|
||||
} else if (url.includes("_/")) {
|
||||
return GameConnexionTypes.anonymous;
|
||||
} else if (url.includes('@/')) {
|
||||
} else if (url.includes("@/")) {
|
||||
return GameConnexionTypes.organization;
|
||||
} else if(url.includes('register/')) {
|
||||
} else if (url.includes("register/")) {
|
||||
return GameConnexionTypes.register;
|
||||
} else if(url === '/') {
|
||||
} else if (url === "/") {
|
||||
return GameConnexionTypes.empty;
|
||||
} else {
|
||||
return GameConnexionTypes.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public getOrganizationToken(): string|null {
|
||||
public getOrganizationToken(): string | null {
|
||||
const match = /\/register\/(.+)/.exec(window.location.pathname.toString());
|
||||
return match ? match [1] : null;
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
public pushRoomIdToUrl(room:Room): void {
|
||||
public pushRoomIdToUrl(room: Room): void {
|
||||
if (window.location.pathname === room.id) return;
|
||||
const hash = window.location.hash;
|
||||
const search = room.search.toString();
|
||||
history.pushState({}, 'WorkAdventure', room.id+(search?'?'+search:'')+hash);
|
||||
history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash);
|
||||
}
|
||||
|
||||
public getStartLayerNameFromUrl(): string|null {
|
||||
public getStartLayerNameFromUrl(): string | null {
|
||||
const hash = window.location.hash;
|
||||
return hash.length > 1 ? hash.substring(1) : null;
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ module.exports = {
|
|||
DISABLE_NOTIFICATIONS: false,
|
||||
PUSHER_URL: undefined,
|
||||
UPLOADER_URL: null,
|
||||
ADMIN_URL: null,
|
||||
ADMIN_URL: undefined,
|
||||
DEBUG_MODE: null,
|
||||
STUN_SERVER: null,
|
||||
TURN_SERVER: null,
|
||||
|
|
|
@ -291,6 +291,18 @@
|
|||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/uuid@8.3.0":
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
|
||||
integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
|
||||
|
||||
"@types/uuidv4@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuidv4/-/uuidv4-5.0.0.tgz#2c94e67b0c06d5adb28fb7ced1a1b5f0866ecd50"
|
||||
integrity sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A==
|
||||
dependencies:
|
||||
uuidv4 "*"
|
||||
|
||||
"@types/webpack-dev-server@^3.11.4":
|
||||
version "3.11.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.4.tgz#90d47dd660b696d409431ab8c1e9fa3615103a07"
|
||||
|
@ -5787,11 +5799,24 @@ utils-merge@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||
|
||||
uuid@8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^3.3.2, uuid@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuidv4@*, uuidv4@^6.2.10:
|
||||
version "6.2.10"
|
||||
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.10.tgz#42fc1c12b6f85ad536c2c5c1e836079d1e15003c"
|
||||
integrity sha512-FMo1exd9l5UvoUPHRR6NrtJ/OJRePh0ca7IhPwBuMNuYRqjtuh8lE3WDxAUvZ4Yss5FbCOsPFjyWJf9lVTEmnw==
|
||||
dependencies:
|
||||
"@types/uuid" "8.3.0"
|
||||
uuid "8.3.2"
|
||||
|
||||
v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue