latest dev
This commit is contained in:
parent
22ff1cc82e
commit
f320184d23
@ -1,6 +1,6 @@
|
||||
<script lang="typescript">
|
||||
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||
import { requestedScreenSharingState, screenSharingAvailableStore } from "../Stores/ScreenSharingStore";
|
||||
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
|
||||
import monitorImg from "./images/monitor.svg";
|
||||
import monitorCloseImg from "./images/monitor-close.svg";
|
||||
import cinemaImg from "./images/cinema.svg";
|
||||
@ -9,9 +9,10 @@
|
||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||
import layoutPresentationImg from "./images/layout-presentation.svg";
|
||||
import layoutChatImg from "./images/layout-chat.svg";
|
||||
import {layoutModeStore} from "../Stores/StreamableCollectionStore";
|
||||
import {LayoutMode} from "../WebRtc/LayoutManager";
|
||||
import {peerStore} from "../Stores/PeerStore";
|
||||
import { layoutModeStore } from "../Stores/StreamableCollectionStore";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { peerStore } from "../Stores/PeerStore";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if ($requestedScreenSharingState === true) {
|
||||
@ -44,37 +45,47 @@
|
||||
$layoutModeStore = LayoutMode.Presentation;
|
||||
}
|
||||
}
|
||||
|
||||
let isSilent: boolean;
|
||||
const unsubscribeIsSilent = isSilentStore.subscribe(value => {
|
||||
isSilent = value;
|
||||
});
|
||||
onDestroy(unsubscribeIsSilent);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size===0}>
|
||||
{#if $layoutModeStore === LayoutMode.Presentation }
|
||||
<img src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode">
|
||||
<img src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode">
|
||||
{:else}
|
||||
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode">
|
||||
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
|
||||
{#if $requestedMicrophoneState}
|
||||
<img src={microphoneImg} alt="Turn on microphone">
|
||||
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState ||
|
||||
isSilent}>
|
||||
{#if $requestedMicrophoneState && !isSilent}
|
||||
<img src={microphoneImg} alt="Turn on microphone">
|
||||
{:else}
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState}>
|
||||
{#if $requestedCameraState}
|
||||
<img src={cinemaImg} alt="Turn on webcam">
|
||||
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState ||
|
||||
isSilent}>
|
||||
{#if $requestedCameraState && !isSilent}
|
||||
<img src={cinemaImg} alt="Turn on webcam">
|
||||
{:else}
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-monitor nes-btn is-dark" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState}
|
||||
<img src={monitorImg} alt="Start screen sharing">
|
||||
<div class="btn-monitor nes-btn is-dark" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore
|
||||
|| isSilent} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState && !isSilent}
|
||||
<img src={monitorImg} alt="Start screen sharing">
|
||||
{:else}
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,13 +1,17 @@
|
||||
<script lang="typescript">
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
import {menuIconVisiblilityStore, menuVisiblilityStore} from "../../Stores/MenuStore";
|
||||
import {menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected} from "../../Stores/MenuStore";
|
||||
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
|
||||
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
|
||||
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||
//import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {PROFILE_URL} from "../../Enum/EnvironmentVariable";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {enableCameraSceneVisibilityStore} from "../../Stores/MediaStore";
|
||||
|
||||
|
||||
function disableMenuStores(){
|
||||
@ -33,22 +37,50 @@
|
||||
gameManager.leaveGame(SelectCharacterSceneName,new SelectCharacterScene());
|
||||
}
|
||||
|
||||
function logOut(){
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
connectionManager.logout();
|
||||
}
|
||||
|
||||
function getProfileUrl(){
|
||||
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
|
||||
}
|
||||
|
||||
function openEnableCameraScene(){
|
||||
disableMenuStores();
|
||||
enableCameraSceneVisibilityStore.showEnableCameraScene();
|
||||
gameManager.leaveGame(EnableCameraSceneName,new EnableCameraScene());
|
||||
}
|
||||
|
||||
//TODO: Uncomment when login will be completely developed
|
||||
/*function clickLogin() {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
}*/
|
||||
|
||||
</script>
|
||||
|
||||
<div class="customize-main">
|
||||
<!-- <section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button>
|
||||
</section> -->
|
||||
{#if $userIsConnected}
|
||||
<section>
|
||||
{#if PROFILE_URL != undefined}
|
||||
<iframe title="profile" src="{getProfileUrl()}"></iframe>
|
||||
{/if}
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}>Log out</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section>
|
||||
<a type="button" class="nes-btn" href="/login">Sing in</a>
|
||||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<!-- <button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button> -->
|
||||
<button type="button" class="nes-btn is-rounded" on:click|preventDefault={openEditSkinScene}>Edit Skin</button>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>Setup camera</button>
|
||||
</section>
|
||||
<!-- <section>
|
||||
<button type="button" class="nes-btn is-primary" on:click|preventDefault={clickLogin}>Login</button>
|
||||
@ -63,6 +95,12 @@
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
iframe{
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 50px;
|
||||
width: 250px;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="typescript">
|
||||
import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||
import {localStreamStore} from "../Stores/MediaStore";
|
||||
import {localStreamStore, isSilentStore} from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import {srcObject} from "./Video/utils";
|
||||
@ -17,14 +17,25 @@
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
|
||||
let isSilent: boolean;
|
||||
const unsubscribeIsSilent = isSilentStore.subscribe(value => {
|
||||
isSilent = value;
|
||||
});
|
||||
|
||||
onDestroy(unsubscribeIsSilent);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="video-container nes-container is-rounded is-dark div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video}>
|
||||
<div class="video-container nes-container is-rounded is-dark div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
|
||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video>
|
||||
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="is-silent" class:hide={isSilent}>
|
||||
Silent zone
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,6 +7,8 @@ import { localUserStore } from "./LocalUserStore";
|
||||
import { CharacterTexture, LocalUser } from "./LocalUser";
|
||||
import { Room } from "./Room";
|
||||
import { _ServiceWorker } from "../Network/ServiceWorker";
|
||||
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
|
||||
import { userIsConnected } from "../Stores/MenuStore";
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!: LocalUser;
|
||||
@ -15,6 +17,7 @@ class ConnectionManager {
|
||||
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
||||
private _unloading: boolean = false;
|
||||
private authToken: string | null = null;
|
||||
private _currentRoom: Room | null = null;
|
||||
|
||||
private serviceWorker?: _ServiceWorker;
|
||||
|
||||
@ -30,9 +33,9 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Promise<void>
|
||||
* TODO fix me to be move in game manager
|
||||
*/
|
||||
public loadOpenIDScreen(): Promise<void> {
|
||||
public loadOpenIDScreen() {
|
||||
const state = localUserStore.generateState();
|
||||
const nonce = localUserStore.generateNonce();
|
||||
|
||||
@ -49,21 +52,32 @@ class ConnectionManager {
|
||||
|
||||
localUserStore.setAuthToken(null);
|
||||
|
||||
//TODO refactor this and don't realise previous call
|
||||
return Axios.get(loginUrl)
|
||||
.then(() => {
|
||||
window.location.assign(loginUrl);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err, "We don't have URL to regenerate authentication user");
|
||||
//TODO show modal login
|
||||
window.location.reload();
|
||||
});
|
||||
//TODO fix me to redirect this URL by pusher
|
||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
}
|
||||
const redirectUrl = `${this._currentRoom.iframeAuthentication}?state=${state}&nonce=${nonce}`;
|
||||
window.location.assign(redirectUrl);
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
public logout() {
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
public async logout() {
|
||||
//user logout, set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
//Logout user in pusher and hydra
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(null);
|
||||
window.location.reload();
|
||||
|
||||
//Go on login page can permit to clear token and start authentication process
|
||||
window.location.assign("/login");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,8 +86,13 @@ class ConnectionManager {
|
||||
public async initGameConnexion(): Promise<Room> {
|
||||
const connexionType = urlManager.getGameConnexionType();
|
||||
this.connexionType = connexionType;
|
||||
let room: Room | null = null;
|
||||
if (connexionType === GameConnexionTypes.jwt) {
|
||||
this._currentRoom = null;
|
||||
if (connexionType === GameConnexionTypes.login) {
|
||||
//TODO clear all cash and redirect on login scene (iframe)
|
||||
localUserStore.setAuthToken(null);
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
@ -83,24 +102,15 @@ class ConnectionManager {
|
||||
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;
|
||||
|
||||
let roomPath: string;
|
||||
|
||||
roomPath = window.location.protocol + "//" + window.location.host + START_ROOM_URL;
|
||||
|
||||
const lastRoomUrl = localUserStore.getLastRoomUrl();
|
||||
if (lastRoomUrl != undefined && lastRoomUrl != "") {
|
||||
roomPath = lastRoomUrl;
|
||||
localUserStore.setCode(code);
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadOpenIDScreen();
|
||||
}
|
||||
|
||||
room = await Room.createRoom(new URL(roomPath));
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.register) {
|
||||
//@deprecated
|
||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||
@ -114,7 +124,7 @@ class ConnectionManager {
|
||||
|
||||
const roomUrl = data.roomUrl;
|
||||
|
||||
room = await Room.createRoom(
|
||||
this._currentRoom = await Room.createRoom(
|
||||
new URL(
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
@ -124,7 +134,7 @@ class ConnectionManager {
|
||||
window.location.hash
|
||||
)
|
||||
);
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (
|
||||
connexionType === GameConnexionTypes.organization ||
|
||||
connexionType === GameConnexionTypes.anonymous ||
|
||||
@ -134,13 +144,18 @@ class ConnectionManager {
|
||||
//todo: add here some kind of warning if authToken has expired.
|
||||
if (!this.authToken) {
|
||||
await this.anonymousLogin();
|
||||
// this.loadOpenIDScreen();
|
||||
} else {
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||
|
||||
let roomPath: string;
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
roomPath = window.location.protocol + "//" + window.location.host + START_ROOM_URL;
|
||||
roomPath = localUserStore.getLastRoomUrl();
|
||||
//get last room path from cache api
|
||||
try {
|
||||
const lastRoomUrl = await localUserStore.getLastRoomUrlCacheApi();
|
||||
@ -161,13 +176,13 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
//get detail map for anonymous login and set texture in local storage
|
||||
room = await Room.createRoom(new URL(roomPath));
|
||||
if (room.textures != undefined && room.textures.length > 0) {
|
||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if (this.localUser.textures.length === 0) {
|
||||
this.localUser.textures = room.textures;
|
||||
this.localUser.textures = this._currentRoom.textures;
|
||||
} else {
|
||||
room.textures.forEach((newTexture) => {
|
||||
this._currentRoom.textures.forEach((newTexture) => {
|
||||
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
||||
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||
return;
|
||||
@ -178,12 +193,12 @@ class ConnectionManager {
|
||||
localUserStore.saveUser(this.localUser);
|
||||
}
|
||||
}
|
||||
if (room == undefined) {
|
||||
if (this._currentRoom == undefined) {
|
||||
return Promise.reject(new Error("Invalid URL"));
|
||||
}
|
||||
|
||||
this.serviceWorker = new _ServiceWorker();
|
||||
return Promise.resolve(room);
|
||||
return Promise.resolve(this._currentRoom);
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
@ -238,9 +253,6 @@ class ConnectionManager {
|
||||
});
|
||||
|
||||
connection.onConnect((connect: OnConnectInterface) => {
|
||||
//save last room url connected
|
||||
localUserStore.setLastRoomUrl(roomUrl);
|
||||
|
||||
resolve(connect);
|
||||
});
|
||||
}).catch((err) => {
|
||||
@ -260,6 +272,34 @@ class ConnectionManager {
|
||||
get getConnexionType() {
|
||||
return this.connexionType;
|
||||
}
|
||||
|
||||
async checkAuthUserConnexion() {
|
||||
//set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
const state = localUserStore.getState();
|
||||
const code = localUserStore.getCode();
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.authToken = authToken;
|
||||
|
||||
//user connected, set connected store for menu at true
|
||||
userIsConnected.set(true);
|
||||
}
|
||||
|
||||
get currentRoom() {
|
||||
return this._currentRoom;
|
||||
}
|
||||
}
|
||||
|
||||
export const connectionManager = new ConnectionManager();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { START_ROOM_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const playerNameKey = "playerName";
|
||||
const selectedPlayerKey = "selectedPlayer";
|
||||
@ -17,6 +18,8 @@ const authToken = "authToken";
|
||||
const state = "state";
|
||||
const nonce = "nonce";
|
||||
const notification = "notificationPermission";
|
||||
const code = "code";
|
||||
const cameraSetup = "cameraSetup";
|
||||
|
||||
const cacheAPIIndex = "workavdenture-cache";
|
||||
|
||||
@ -125,7 +128,9 @@ class LocalUserStore {
|
||||
});
|
||||
}
|
||||
getLastRoomUrl(): string {
|
||||
return localStorage.getItem(lastRoomUrl) ?? "";
|
||||
return (
|
||||
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
|
||||
);
|
||||
}
|
||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||
return caches.open(cacheAPIIndex).then((cache) => {
|
||||
@ -160,19 +165,32 @@ class LocalUserStore {
|
||||
|
||||
verifyState(value: string): boolean {
|
||||
const oldValue = localStorage.getItem(state);
|
||||
localStorage.removeItem(state);
|
||||
return oldValue === value;
|
||||
}
|
||||
getState(): string | null {
|
||||
return localStorage.getItem(state);
|
||||
}
|
||||
generateNonce(): string {
|
||||
const newNonce = uuidv4();
|
||||
localStorage.setItem(nonce, newNonce);
|
||||
return newNonce;
|
||||
}
|
||||
|
||||
getNonce(): string | null {
|
||||
const oldValue = localStorage.getItem(nonce);
|
||||
localStorage.removeItem(nonce);
|
||||
return oldValue;
|
||||
return localStorage.getItem(nonce);
|
||||
}
|
||||
setCode(value: string): void {
|
||||
localStorage.setItem(code, value);
|
||||
}
|
||||
getCode(): string | null {
|
||||
return localStorage.getItem(code);
|
||||
}
|
||||
|
||||
setCameraSetup(cameraId: string) {
|
||||
localStorage.setItem(cameraSetup, cameraId);
|
||||
}
|
||||
getCameraSetup(): { video: unknown; audio: unknown } | undefined {
|
||||
const cameraSetupValues = localStorage.getItem(cameraSetup);
|
||||
return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ export interface RoomRedirect {
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private _authenticationMandatory: boolean = false;
|
||||
private _iframeAuthentication?: string;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
private instance: string | undefined;
|
||||
@ -101,6 +103,8 @@ export class Room {
|
||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
||||
this._iframeAuthentication = data.iframeAuthentication;
|
||||
return new MapDetail(data.mapUrl, data.textures);
|
||||
}
|
||||
|
||||
@ -186,4 +190,12 @@ export class Room {
|
||||
}
|
||||
return this._mapUrl;
|
||||
}
|
||||
|
||||
get authenticationMandatory(): boolean {
|
||||
return this._authenticationMandatory;
|
||||
}
|
||||
|
||||
get iframeAuthentication(): string | undefined {
|
||||
return this._iframeAuthentication;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,11 @@ export class RoomConnection implements RoomConnection {
|
||||
*
|
||||
* @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]"
|
||||
* @param name
|
||||
* @param characterLayers
|
||||
* @param position
|
||||
* @param viewport
|
||||
* @param companion
|
||||
*/
|
||||
public constructor(
|
||||
token: string | null,
|
||||
@ -218,7 +223,7 @@ export class RoomConnection implements RoomConnection {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasTokenexpiredmessage()) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
connectionManager.logout();
|
||||
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());
|
||||
|
@ -19,6 +19,7 @@ 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 NODE_ENV = process.env.NODE_ENV || "development";
|
||||
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
||||
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
|
||||
|
@ -70,3 +70,9 @@ export const addLoader = (scene: Phaser.Scene): void => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const removeLoader = (scene: Phaser.Scene): void => {
|
||||
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
scene.load.textureManager.remove(LogoNameIndex);
|
||||
}
|
||||
};
|
||||
|
@ -1,29 +1,30 @@
|
||||
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
|
||||
import {SpeechBubble} from "./SpeechBubble";
|
||||
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||
import { SpeechBubble } from "./SpeechBubble";
|
||||
import Text = Phaser.GameObjects.Text;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {TextureError} from "../../Exception/TextureError";
|
||||
import {Companion} from "../Companion/Companion";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import { TextureError } from "../../Exception/TextureError";
|
||||
import { Companion } from "../Companion/Companion";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
import { isSilentStore } from "../../Stores/MediaStore";
|
||||
|
||||
const playerNameY = - 25;
|
||||
const playerNameY = -25;
|
||||
|
||||
interface AnimationData {
|
||||
key: string;
|
||||
frameRate: number;
|
||||
repeat: number;
|
||||
frameModel: string; //todo use an enum
|
||||
frames : number[]
|
||||
frames: number[];
|
||||
}
|
||||
|
||||
const interactiveRadius = 35;
|
||||
|
||||
export abstract class Character extends Container {
|
||||
private bubble: SpeechBubble|null = null;
|
||||
private bubble: SpeechBubble | null = null;
|
||||
private readonly playerName: Text;
|
||||
public PlayerValue: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
@ -35,32 +36,38 @@ export abstract class Character extends Container {
|
||||
private emoteTween: Phaser.Tweens.Tween|null = null;
|
||||
scene: GameScene;
|
||||
|
||||
constructor(scene: GameScene,
|
||||
x: number,
|
||||
y: number,
|
||||
texturesPromise: Promise<string[]>,
|
||||
name: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
frame: string | number,
|
||||
isClickable: boolean,
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
constructor(
|
||||
scene: GameScene,
|
||||
x: number,
|
||||
y: number,
|
||||
texturesPromise: Promise<string[]>,
|
||||
name: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
frame: string | number,
|
||||
isClickable: boolean,
|
||||
companion: string | null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(scene, x, y/*, texture, frame*/);
|
||||
super(scene, x, y /*, texture, frame*/);
|
||||
this.scene = scene;
|
||||
this.PlayerValue = name;
|
||||
this.invisible = true
|
||||
this.invisible = true;
|
||||
|
||||
this.sprites = new Map<string, Sprite>();
|
||||
|
||||
//textures are inside a Promise in case they need to be lazyloaded before use.
|
||||
texturesPromise.then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false
|
||||
})
|
||||
this.invisible = false;
|
||||
});
|
||||
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "#000"});
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {
|
||||
fontFamily: '"Press Start 2P"',
|
||||
fontSize: "8px",
|
||||
strokeThickness: 2,
|
||||
stroke: "#000",
|
||||
});
|
||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerName);
|
||||
|
||||
@ -71,18 +78,17 @@ export abstract class Character extends Container {
|
||||
useHandCursor: true,
|
||||
});
|
||||
|
||||
this.on('pointerover',() => {
|
||||
this.on("pointerover", () => {
|
||||
this.getOutlinePlugin()?.add(this.playerName, {
|
||||
thickness: 2,
|
||||
outlineColor: 0xffff00
|
||||
outlineColor: 0xffff00,
|
||||
});
|
||||
this.scene.markDirty();
|
||||
});
|
||||
this.on('pointerout',() => {
|
||||
this.on("pointerout", () => {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.scene.markDirty();
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
scene.add.existing(this);
|
||||
@ -97,38 +103,38 @@ export abstract class Character extends Container {
|
||||
|
||||
this.playAnimation(direction, moving);
|
||||
|
||||
if (typeof companion === 'string') {
|
||||
if (typeof companion === "string") {
|
||||
this.addCompanion(companion, companionTexturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
private getOutlinePlugin(): OutlinePipelinePlugin|undefined {
|
||||
return this.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined;
|
||||
private getOutlinePlugin(): OutlinePipelinePlugin | undefined {
|
||||
return this.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||
}
|
||||
|
||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||
if (typeof texturePromise !== 'undefined') {
|
||||
if (typeof texturePromise !== "undefined") {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
public addTextures(textures: string[], frame?: string | number): void {
|
||||
for (const texture of textures) {
|
||||
if(this.scene && !this.scene.textures.exists(texture)){
|
||||
throw new TextureError('texture not found');
|
||||
if (this.scene && !this.scene.textures.exists(texture)) {
|
||||
throw new TextureError("texture not found");
|
||||
}
|
||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||
this.add(sprite);
|
||||
this.getPlayerAnimations(texture).forEach(d => {
|
||||
this.getPlayerAnimations(texture).forEach((d) => {
|
||||
this.scene.anims.create({
|
||||
key: d.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {frames: d.frames}),
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, { frames: d.frames }),
|
||||
frameRate: d.frameRate,
|
||||
repeat: d.repeat
|
||||
repeat: d.repeat,
|
||||
});
|
||||
})
|
||||
});
|
||||
// Needed, otherwise, animations are not handled correctly.
|
||||
if(this.scene) {
|
||||
if (this.scene) {
|
||||
this.scene.sys.updateList.add(sprite);
|
||||
}
|
||||
this.sprites.set(texture, sprite);
|
||||
@ -136,68 +142,77 @@ export abstract class Character extends Container {
|
||||
}
|
||||
|
||||
private getPlayerAnimations(name: string): AnimationData[] {
|
||||
return [{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
},{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}];
|
||||
return [
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected playAnimation(direction : PlayerAnimationDirections, moving: boolean): void {
|
||||
protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
|
||||
if (this.invisible) return;
|
||||
for (const [texture, sprite] of this.sprites.entries()) {
|
||||
if (!sprite.anims) {
|
||||
console.error('ANIMS IS NOT DEFINED!!!');
|
||||
console.error("ANIMS IS NOT DEFINED!!!");
|
||||
return;
|
||||
}
|
||||
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
|
||||
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true);
|
||||
sprite.play(texture + "-" + direction + "-" + PlayerAnimationTypes.Walk, true);
|
||||
} else if (!moving) {
|
||||
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true);
|
||||
sprite.anims.play(texture + "-" + direction + "-" + PlayerAnimationTypes.Idle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -205,7 +220,7 @@ export abstract class Character extends Container {
|
||||
protected getBody(): Phaser.Physics.Arcade.Body {
|
||||
const body = this.body;
|
||||
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
|
||||
throw new Error('Container does not have arcade body');
|
||||
throw new Error("Container does not have arcade body");
|
||||
}
|
||||
return body;
|
||||
}
|
||||
@ -216,16 +231,20 @@ export abstract class Character extends Container {
|
||||
body.setVelocity(x, y);
|
||||
|
||||
// up or down animations are prioritized over left and right
|
||||
if (body.velocity.y < 0) { //moving up
|
||||
if (body.velocity.y < 0) {
|
||||
//moving up
|
||||
this.lastDirection = PlayerAnimationDirections.Up;
|
||||
this.playAnimation(PlayerAnimationDirections.Up, true);
|
||||
} else if (body.velocity.y > 0) { //moving down
|
||||
} else if (body.velocity.y > 0) {
|
||||
//moving down
|
||||
this.lastDirection = PlayerAnimationDirections.Down;
|
||||
this.playAnimation(PlayerAnimationDirections.Down, true);
|
||||
} else if (body.velocity.x > 0) { //moving right
|
||||
} else if (body.velocity.x > 0) {
|
||||
//moving right
|
||||
this.lastDirection = PlayerAnimationDirections.Right;
|
||||
this.playAnimation(PlayerAnimationDirections.Right, true);
|
||||
} else if (body.velocity.x < 0) { //moving left
|
||||
} else if (body.velocity.x < 0) {
|
||||
//moving left
|
||||
this.lastDirection = PlayerAnimationDirections.Left;
|
||||
this.playAnimation(PlayerAnimationDirections.Left, true);
|
||||
}
|
||||
@ -237,32 +256,39 @@ export abstract class Character extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
stop(){
|
||||
stop() {
|
||||
this.getBody().setVelocity(0, 0);
|
||||
this.playAnimation(this.lastDirection, false);
|
||||
}
|
||||
|
||||
say(text: string) {
|
||||
if (this.bubble) return;
|
||||
this.bubble = new SpeechBubble(this.scene, this, text)
|
||||
this.bubble = new SpeechBubble(this.scene, this, text);
|
||||
setTimeout(() => {
|
||||
if (this.bubble !== null) {
|
||||
this.bubble.destroy();
|
||||
this.bubble = null;
|
||||
}
|
||||
}, 3000)
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
for (const sprite of this.sprites.values()) {
|
||||
if(this.scene) {
|
||||
if (this.scene) {
|
||||
this.scene.sys.updateList.remove(sprite);
|
||||
}
|
||||
}
|
||||
this.list.forEach(objectContaining => objectContaining.destroy())
|
||||
this.list.forEach((objectContaining) => objectContaining.destroy());
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
isSilent() {
|
||||
isSilentStore.set(true);
|
||||
}
|
||||
noSilent() {
|
||||
isSilentStore.set(false);
|
||||
}
|
||||
|
||||
playEmote(emote: string) {
|
||||
this.cancelPreviousEmote();
|
||||
|
||||
@ -284,11 +310,11 @@ export abstract class Character extends Container {
|
||||
alpha: 1,
|
||||
y: emoteY,
|
||||
},
|
||||
ease: 'Power2',
|
||||
ease: "Power2",
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
this.startPulseTransition(emoteY, scalingFactor);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -297,7 +323,7 @@ export abstract class Character extends Container {
|
||||
targets: this.emote,
|
||||
props: {
|
||||
y: emoteY * 1.3,
|
||||
scale: scalingFactor * 1.1
|
||||
scale: scalingFactor * 1.1,
|
||||
},
|
||||
duration: 250,
|
||||
yoyo: true,
|
||||
@ -305,7 +331,7 @@ export abstract class Character extends Container {
|
||||
completeDelay: 200,
|
||||
onComplete: () => {
|
||||
this.startExitTransition(emoteY);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -316,11 +342,11 @@ export abstract class Character extends Container {
|
||||
alpha: 0,
|
||||
y: 2 * emoteY,
|
||||
},
|
||||
ease: 'Power2',
|
||||
ease: "Power2",
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
this.destroyEmote();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -328,7 +354,7 @@ export abstract class Character extends Container {
|
||||
if (!this.emote) return;
|
||||
|
||||
this.emoteTween?.remove();
|
||||
this.destroyEmote()
|
||||
this.destroyEmote();
|
||||
}
|
||||
|
||||
private destroyEmote() {
|
||||
|
@ -11,10 +11,10 @@ export class EmoteManager {
|
||||
if(actor) {
|
||||
actor.playEmote(event.emote);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export class GameManager {
|
||||
private characterLayers: string[] | null;
|
||||
private companion: string | null;
|
||||
private startRoom!: Room;
|
||||
private cameraSetup?: { video: unknown; audio: unknown };
|
||||
currentGameSceneName: string | null = null;
|
||||
// Note: this scenePlugin is the scenePlugin of the EntryScene. We should always provide a key in methods called on this scenePlugin.
|
||||
private scenePlugin!: Phaser.Scenes.ScenePlugin;
|
||||
@ -27,6 +28,7 @@ export class GameManager {
|
||||
this.playerName = localUserStore.getName();
|
||||
this.characterLayers = localUserStore.getCharacterLayers();
|
||||
this.companion = localUserStore.getCompanion();
|
||||
this.cameraSetup = localUserStore.getCameraSetup();
|
||||
}
|
||||
|
||||
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
|
||||
@ -39,12 +41,17 @@ export class GameManager {
|
||||
this.playerName = res.headers['bstlyusername'];
|
||||
}
|
||||
|
||||
if (!this.playerName) {
|
||||
//If player name was not set show login scene with player name
|
||||
//If Room si not public and Auth was not set, show login scene to authenticate user (OpenID - SSO - Anonymous)
|
||||
if (!this.playerName || (this.startRoom.authenticationMandatory && !localUserStore.getAuthToken())) {
|
||||
return LoginSceneName;
|
||||
} else if (!this.characterLayers || !this.characterLayers.length) {
|
||||
return SelectCharacterSceneName;
|
||||
} else {
|
||||
} else if (this.cameraSetup == undefined) {
|
||||
return EnableCameraSceneName;
|
||||
} else {
|
||||
this.activeMenuSceneAndHelpCameraSettings();
|
||||
return this.startRoom.key;
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +97,14 @@ export class GameManager {
|
||||
public goToStartingMap(): void {
|
||||
console.log("starting " + (this.currentGameSceneName || this.startRoom.key));
|
||||
this.scenePlugin.start(this.currentGameSceneName || this.startRoom.key);
|
||||
this.activeMenuSceneAndHelpCameraSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return void
|
||||
*/
|
||||
private activeMenuSceneAndHelpCameraSettings(): void {
|
||||
if (
|
||||
!localUserStore.getHelpCameraSettingsShown() &&
|
||||
(!get(requestedMicrophoneState) || !get(requestedCameraState))
|
||||
@ -137,6 +151,10 @@ export class GameManager {
|
||||
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
||||
return this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
||||
}
|
||||
|
||||
public get currentStartedRoom() {
|
||||
return this.startRoom;
|
||||
}
|
||||
}
|
||||
|
||||
export const gameManager = new GameManager();
|
||||
|
@ -37,7 +37,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { addLoader, removeLoader } from "../Components/Loader";
|
||||
import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
@ -93,6 +93,7 @@ import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||
import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
|
||||
import type { RadialMenuItem } from "../Components/RadialMenu";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
@ -291,8 +292,11 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
|
||||
//once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading.
|
||||
console.error("Error when loading: ", file);
|
||||
if (this.preloading) {
|
||||
//remove loader in progress
|
||||
removeLoader(this);
|
||||
|
||||
//display an error scene
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: "Network error",
|
||||
subTitle: "An error occurred while loading resource:",
|
||||
@ -850,8 +854,10 @@ export class GameScene extends DirtyScene {
|
||||
this.gameMap.onPropertyChange("silent", (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === "") {
|
||||
this.connection?.setSilent(false);
|
||||
this.CurrentPlayer.noSilent();
|
||||
} else {
|
||||
this.connection?.setSilent(true);
|
||||
this.CurrentPlayer.isSilent();
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange("playAudio", (newValue, oldValue, allProps) => {
|
||||
@ -1294,7 +1300,9 @@ export class GameScene extends DirtyScene {
|
||||
if (!targetRoom.isEqual(this.room)) {
|
||||
if (this.scene.get(targetRoom.key) === null) {
|
||||
console.error("next room not loaded", targetRoom.key);
|
||||
return;
|
||||
// Try to load next dame room from exit URL
|
||||
// The policy of room can to be updated during a session and not load before
|
||||
await this.loadNextGameFromExitUrl(targetRoom.key);
|
||||
}
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import type { GameMap } from "./GameMap";
|
||||
import type {ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer} from "../Map/ITiledMap";
|
||||
import type { ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer } from "../Map/ITiledMap";
|
||||
|
||||
interface Variable {
|
||||
defaultValue: unknown;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
|
||||
export const EnableCameraSceneName = "EnableCameraScene";
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { loginSceneVisibleIframeStore, loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
|
||||
export const LoginSceneName = "LoginScene";
|
||||
|
||||
@ -18,6 +20,16 @@ export class LoginScene extends ResizableScene {
|
||||
preload() {}
|
||||
|
||||
create() {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
//If authentication is mandatory, push authentication iframe
|
||||
if (
|
||||
localUserStore.getAuthToken() == undefined &&
|
||||
gameManager.currentStartedRoom &&
|
||||
gameManager.currentStartedRoom?.authenticationMandatory
|
||||
) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
loginSceneVisibleIframeStore.set(true);
|
||||
}
|
||||
loginSceneVisibleStore.set(true);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
|
@ -23,7 +23,7 @@ export class Player extends Character {
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
private userInputManager: UserInputManager,
|
||||
companion: string|null,
|
||||
companion: string | null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||
@ -37,7 +37,7 @@ export class Player extends Character {
|
||||
this.emoteMenu.y = this.y;
|
||||
}
|
||||
};
|
||||
this.scene.events.addListener('postupdate', this.updateListener);
|
||||
this.scene.events.addListener("postupdate", this.updateListener);
|
||||
}
|
||||
|
||||
moveUser(delta: number): void {
|
||||
@ -73,14 +73,14 @@ export class Player extends Character {
|
||||
|
||||
if (x !== 0 || y !== 0) {
|
||||
this.move(x, y);
|
||||
this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y});
|
||||
this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y });
|
||||
} else if (this.wasMoving && moving) {
|
||||
// slow joystick movement
|
||||
this.move(0, 0);
|
||||
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
|
||||
this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y });
|
||||
} else if (this.wasMoving && !moving) {
|
||||
this.stop();
|
||||
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y});
|
||||
this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y });
|
||||
}
|
||||
|
||||
if (direction !== null) {
|
||||
@ -117,13 +117,20 @@ export class Player extends Character {
|
||||
});
|
||||
}
|
||||
|
||||
isSilent() {
|
||||
super.isSilent();
|
||||
}
|
||||
noSilent() {
|
||||
super.noSilent();
|
||||
}
|
||||
|
||||
closeEmoteMenu(): void {
|
||||
if (!this.emoteMenu) return;
|
||||
this.emoteMenu.closePicker();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.scene.events.removeListener('postupdate', this.updateListener);
|
||||
this.scene.events.removeListener("postupdate", this.updateListener);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
10
front/src/Stores/Errors/MediaStreamConstraintsError.ts
Normal file
10
front/src/Stores/Errors/MediaStreamConstraintsError.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export class MediaStreamConstraintsError extends Error {
|
||||
static NAME = "MediaStreamConstraintsError";
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
"Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome."
|
||||
);
|
||||
this.name = MediaStreamConstraintsError.NAME;
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const loginSceneVisibleStore = writable(false);
|
||||
export const loginSceneVisibleIframeStore = writable(false);
|
||||
|
@ -9,6 +9,7 @@ import { WebviewOnOldIOS } from "./Errors/WebviewOnOldIOS";
|
||||
import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility";
|
||||
import { peerStore } from "./PeerStore";
|
||||
import { privacyShutdownStore } from "./PrivacyShutdownStore";
|
||||
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
|
||||
|
||||
/**
|
||||
* A store that contains the camera state requested by the user (on or off).
|
||||
@ -255,6 +256,12 @@ export const mediaStreamConstraintsStore = derived(
|
||||
video: currentVideoConstraint,
|
||||
audio: currentAudioConstraint,
|
||||
});
|
||||
localUserStore.setCameraSetup(
|
||||
JSON.stringify({
|
||||
video: currentVideoConstraint,
|
||||
audio: currentAudioConstraint,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -413,7 +420,7 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
if (constraints.video !== false) {
|
||||
if (constraints.video !== false || constraints.audio !== false) {
|
||||
console.info(
|
||||
"Error. Unable to get microphone and/or camera access. Trying audio only.",
|
||||
$mediaStreamConstraintsStore,
|
||||
@ -425,7 +432,17 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
||||
error: e,
|
||||
});
|
||||
// Let's try without video constraints
|
||||
requestedCameraState.disableWebcam();
|
||||
if (constraints.video !== false) {
|
||||
requestedCameraState.disableWebcam();
|
||||
}
|
||||
if (constraints.audio !== false) {
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
}
|
||||
} else if (!constraints.video && !constraints.audio) {
|
||||
set({
|
||||
type: "error",
|
||||
error: new MediaStreamConstraintsError(),
|
||||
});
|
||||
} else {
|
||||
console.info(
|
||||
"Error. Unable to get microphone and/or camera access.",
|
||||
@ -561,3 +578,8 @@ localStreamStore.subscribe((streamResult) => {
|
||||
* A store containing the real active media is mobile
|
||||
*/
|
||||
export const obtainedMediaConstraintIsMobileStore = writable(false);
|
||||
|
||||
/**
|
||||
* A store containing if user is silent, so if he is in silent zone. This permit to show et hide camera of user
|
||||
*/
|
||||
export const isSilentStore = writable(false);
|
||||
|
@ -6,6 +6,8 @@ import { CONTACT_URL } from "../Enum/EnvironmentVariable";
|
||||
export const menuIconVisiblilityStore = writable(false);
|
||||
export const menuVisiblilityStore = writable(false);
|
||||
export const menuInputFocusStore = writable(false);
|
||||
export const loginUrlStore = writable(false);
|
||||
export const userIsConnected = writable(false);
|
||||
|
||||
let warningContainerTimeout: Timeout | null = null;
|
||||
function createWarningContainerStore() {
|
||||
|
@ -2,6 +2,7 @@ import { writable } from "svelte/store";
|
||||
import type { PlayerInterface } from "../Phaser/Game/PlayerInterface";
|
||||
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||
import { getRandomColor } from "../WebRtc/ColorGenerator";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
|
||||
let idCount = 0;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Room } from "../Connexion/Room";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
|
||||
export enum GameConnexionTypes {
|
||||
anonymous = 1,
|
||||
@ -7,13 +8,16 @@ export enum GameConnexionTypes {
|
||||
empty,
|
||||
unknown,
|
||||
jwt,
|
||||
login,
|
||||
}
|
||||
|
||||
//this class is responsible with analysing and editing the game's url
|
||||
class UrlManager {
|
||||
public getGameConnexionType(): GameConnexionTypes {
|
||||
const url = window.location.pathname.toString();
|
||||
if (url === "/jwt") {
|
||||
if (url === "/login") {
|
||||
return GameConnexionTypes.login;
|
||||
} else if (url === "/jwt") {
|
||||
return GameConnexionTypes.jwt;
|
||||
} else if (url.includes("_/")) {
|
||||
return GameConnexionTypes.anonymous;
|
||||
@ -35,6 +39,8 @@ class UrlManager {
|
||||
|
||||
public pushRoomIdToUrl(room: Room): void {
|
||||
if (window.location.pathname === room.id) return;
|
||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||
localUserStore.setLastRoomUrl(room.key);
|
||||
const hash = window.location.hash;
|
||||
const search = room.search.toString();
|
||||
history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash);
|
||||
|
@ -12,6 +12,7 @@ import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility
|
||||
import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||
import { get } from "svelte/store";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
import { MediaStreamConstraintsError } from "../Stores/Errors/MediaStreamConstraintsError";
|
||||
|
||||
export class MediaManager {
|
||||
startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||
@ -24,16 +25,17 @@ export class MediaManager {
|
||||
constructor() {
|
||||
localStreamStore.subscribe((result) => {
|
||||
if (result.type === "error") {
|
||||
console.error(result.error);
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "cameraAccessDenied",
|
||||
type: "warning",
|
||||
message: "Camera access denied. Click here and check your browser permissions.",
|
||||
callback: () => {
|
||||
helpCameraSettingsVisibleStore.set(true);
|
||||
},
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
if (result.error.name !== MediaStreamConstraintsError.NAME) {
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "cameraAccessDenied",
|
||||
type: "warning",
|
||||
message: "Camera access denied. Click here and check your browser permissions.",
|
||||
callback: () => {
|
||||
helpCameraSettingsVisibleStore.set(true);
|
||||
},
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
}
|
||||
//remove it after 10 sec
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("cameraAccessDenied");
|
||||
|
@ -1094,3 +1094,19 @@ div.action.danger p.action-body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.is-silent {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
border-radius: 15px 15px 15px 15px;
|
||||
max-height: 20%;
|
||||
transition: right 350ms;
|
||||
right: -20vw;
|
||||
background-color: black;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
padding: 30px 20px;
|
||||
}
|
||||
div.is-silent.hide {
|
||||
right: 15px;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
||||
import { PROFILE_URL } from "./src/Enum/EnvironmentVariable";
|
||||
|
||||
const mode = process.env.NODE_ENV ?? "development";
|
||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||
@ -191,6 +192,7 @@ module.exports = {
|
||||
UPLOADER_URL: null,
|
||||
ADMIN_URL: undefined,
|
||||
CONTACT_URL: null,
|
||||
PROFILE_URL: null,
|
||||
DEBUG_MODE: null,
|
||||
STUN_SERVER: null,
|
||||
TURN_SERVER: null,
|
||||
|
@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
|
||||
@ -17,6 +17,7 @@ export class AuthenticateController extends BaseController {
|
||||
this.openIDCallback();
|
||||
this.register();
|
||||
this.anonymLogin();
|
||||
this.profileCallback();
|
||||
}
|
||||
|
||||
openIDLogin() {
|
||||
@ -48,14 +49,31 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { code, nonce } = parse(req.getQuery());
|
||||
const { code, nonce, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: token }));
|
||||
} catch (err) {
|
||||
console.info("User was not connected", err);
|
||||
}
|
||||
}
|
||||
|
||||
//user have not token created, check data on hydra and create token
|
||||
const userInfo = await openIDClient.getUserInfo(code as string, nonce as string);
|
||||
const sub = userInfo.sub;
|
||||
if (!sub) {
|
||||
throw new Error("No sub in the response");
|
||||
}
|
||||
const authToken = jwtTokenManager.createAuthToken(sub);
|
||||
const authToken = jwtTokenManager.createAuthToken(sub, userInfo.access_token);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken }));
|
||||
@ -63,6 +81,30 @@ export class AuthenticateController extends BaseController {
|
||||
return this.errorToResponse(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/logout-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
|
||||
const { token } = parse(req.getQuery());
|
||||
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be logout on Hydra");
|
||||
}
|
||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
||||
} catch (error) {
|
||||
console.error("openIDCallback => logout-callback", error);
|
||||
} finally {
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Try to login with an admin token
|
||||
@ -136,4 +178,39 @@ export class AuthenticateController extends BaseController {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
profileCallback() {
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/profile-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { userIdentify, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
|
||||
//get login profile
|
||||
res.writeStatus("302");
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
} catch (error) {
|
||||
return this.errorToResponse(error, res);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorToResponse(error, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||
const API_URL = process.env.API_URL || "";
|
||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "";
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
@ -19,6 +20,7 @@ export {
|
||||
SECRET_KEY,
|
||||
API_URL,
|
||||
ADMIN_API_URL,
|
||||
ADMIN_URL,
|
||||
ADMIN_API_TOKEN,
|
||||
ALLOW_ARTILLERY,
|
||||
CPU_OVERHEAT_THRESHOLD,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
@ -141,6 +141,15 @@ class AdminApi {
|
||||
return data.data;
|
||||
});
|
||||
}
|
||||
|
||||
/*TODO add constant to use profile companny*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!ADMIN_URL) {
|
||||
throw new Error("No admin backoffice set!");
|
||||
}
|
||||
|
||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
||||
|
@ -6,13 +6,13 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||
hydraAccessToken?: string;
|
||||
}
|
||||
export const tokenInvalidException = "tokenInvalid";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createAuthToken(identifier: string) {
|
||||
//TODO fix me 200d when ory authentication will be available
|
||||
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "200d" });
|
||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||
}
|
||||
|
||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Issuer, Client } from "openid-client";
|
||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
@ -31,13 +31,32 @@ class OpenIDClient {
|
||||
});
|
||||
}
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string }> {
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.userinfo(tokenSet);
|
||||
return client.userinfo(tokenSet).then((res) => {
|
||||
return {
|
||||
...res,
|
||||
email: res.email as string,
|
||||
sub: res.sub,
|
||||
access_token: tokenSet.access_token as string,
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public logoutUser(token: string): Promise<void> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.revoke(token);
|
||||
});
|
||||
}
|
||||
|
||||
public checkTokenAuth(token: string): Promise<IntrospectionResponse> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.userinfo(token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const openIDClient = new OpenIDClient();
|
||||
|
Loading…
Reference in New Issue
Block a user