Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
commit
e43e1d8463
@ -19,9 +19,13 @@ ACME_EMAIL=
|
||||
MAX_PER_GROUP=4
|
||||
MAX_USERNAME_LENGTH=8
|
||||
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
OIDC_CLIENT_ISSUER=
|
||||
OPID_CLIENT_ID=
|
||||
OPID_CLIENT_SECRET=
|
||||
OPID_CLIENT_ISSUER=
|
||||
OPID_CLIENT_REDIRECT_URL=
|
||||
OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen
|
||||
OPID_PROFILE_SCREEN_PROVIDER=
|
||||
DISABLE_ANONYMOUS=
|
||||
|
||||
# If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want
|
||||
CONTACT_URL=
|
||||
|
@ -51,9 +51,9 @@ services:
|
||||
JITSI_URL: ${JITSI_URL}
|
||||
JITSI_ISS: ${JITSI_ISS}
|
||||
FRONT_URL : ${FRONT_URL}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_CLIENT_ISSUER: ${OIDC_CLIENT_ISSUER}
|
||||
OPID_CLIENT_ID: ${OPID_CLIENT_ID}
|
||||
OPID_CLIENT_SECRET: ${OPID_CLIENT_SECRET}
|
||||
OPID_CLIENT_ISSUER: ${OPID_CLIENT_ISSUER}
|
||||
labels:
|
||||
- "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher"
|
||||
- "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)"
|
||||
|
@ -62,6 +62,7 @@
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
|
||||
"ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
|
||||
} else {})
|
||||
},
|
||||
"front": {
|
||||
|
@ -40,6 +40,7 @@ services:
|
||||
TURN_USER: ""
|
||||
TURN_PASSWORD: ""
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
@ -67,9 +68,12 @@ services:
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
FRONT_URL: http://localhost
|
||||
OIDC_CLIENT_ID: $OIDC_CLIENT_ID
|
||||
OIDC_CLIENT_SECRET: $OIDC_CLIENT_SECRET
|
||||
OIDC_CLIENT_ISSUER: $OIDC_CLIENT_ISSUER
|
||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
|
||||
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
|
||||
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
|
||||
volumes:
|
||||
- ./pusher:/usr/src/app
|
||||
labels:
|
||||
|
@ -43,6 +43,8 @@ services:
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
@ -68,9 +70,12 @@ services:
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
FRONT_URL: http://play.workadventure.localhost
|
||||
OIDC_CLIENT_ID: $OIDC_CLIENT_ID
|
||||
OIDC_CLIENT_SECRET: $OIDC_CLIENT_SECRET
|
||||
OIDC_CLIENT_ISSUER: $OIDC_CLIENT_ISSUER
|
||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
|
||||
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
|
||||
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
|
||||
volumes:
|
||||
- ./pusher:/usr/src/app
|
||||
labels:
|
||||
|
@ -15,7 +15,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.room.onEnterZone('myZone', () => {
|
||||
WA.room.onEnterLayer('myZone').subscribe(() => {
|
||||
WA.controls.disablePlayerControls();
|
||||
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
|
||||
label: "Got it!",
|
||||
@ -25,5 +25,5 @@ WA.room.onEnterZone('myZone', () => {
|
||||
popup.close();
|
||||
}
|
||||
}]);
|
||||
});
|
||||
})
|
||||
```
|
||||
|
@ -49,7 +49,7 @@ Example:
|
||||
let helloWorldPopup;
|
||||
|
||||
// Open the popup when we enter a given zone
|
||||
helloWorldPopup = WA.room.onEnterZone('myZone', () => {
|
||||
helloWorldPopup = WA.room.onEnterLayer("myZone").subscribe(() => {
|
||||
WA.ui.openPopup("popupRectangle", 'Hello world!', [{
|
||||
label: "Close",
|
||||
className: "primary",
|
||||
@ -57,13 +57,13 @@ helloWorldPopup = WA.room.onEnterZone('myZone', () => {
|
||||
// Close the popup when the "Close" button is pressed.
|
||||
popup.close();
|
||||
}
|
||||
});
|
||||
}]);
|
||||
}]);
|
||||
});
|
||||
|
||||
// Close the popup when we leave the zone.
|
||||
WA.room.onLeaveZone('myZone', () => {
|
||||
WA.room.onLeaveLayer("myZone").subscribe(() => {
|
||||
helloWorldPopup.close();
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
### Add custom menu
|
||||
|
@ -273,7 +273,7 @@ class IframeListener {
|
||||
|
||||
registerScript(scriptUrl: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.log("Loading map related script at ", scriptUrl);
|
||||
console.info("Loading map related script at ", scriptUrl);
|
||||
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||
// Using external iframe mode (
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="typescript">
|
||||
import { requestedScreenSharingState, screenSharingAvailableStore } from "../Stores/ScreenSharingStore";
|
||||
import { isSilentStore, 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,10 +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 { onDestroy } from "svelte";
|
||||
import {layoutModeStore} from "../Stores/StreamableCollectionStore";
|
||||
import {LayoutMode} from "../WebRtc/LayoutManager";
|
||||
import {peerStore} from "../Stores/PeerStore";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if (isSilent) return;
|
||||
|
@ -39,7 +39,7 @@
|
||||
|
||||
})
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
function onKeyDown(e:KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
emoteMenuStore.closeEmoteMenu();
|
||||
}
|
||||
@ -55,7 +55,7 @@
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
<svelte:window on:keydown={onKeyDown}/>
|
||||
|
||||
<div class="emote-menu-container">
|
||||
<div class="emote-menu" bind:this={emojiContainer}></div>
|
||||
|
@ -1,10 +1,60 @@
|
||||
<script lang="ts">
|
||||
|
||||
function goToGettingStarted() {
|
||||
const sparkHost = "https://workadventu.re/getting-started";
|
||||
window.open(sparkHost, "_blank");
|
||||
}
|
||||
|
||||
function goToBuildingMap() {
|
||||
const sparkHost = "https://workadventu.re/map-building/";
|
||||
window.open(sparkHost, "_blank");
|
||||
}
|
||||
|
||||
import {contactPageStore} from "../../Stores/MenuStore";
|
||||
</script>
|
||||
|
||||
<iframe title="contact" src="{$contactPageStore}" allow="clipboard-read; clipboard-write self {$contactPageStore}" allowfullscreen></iframe>
|
||||
<div class="create-map-main">
|
||||
<section class="container-overflow">
|
||||
<section>
|
||||
<h3>Getting started</h3>
|
||||
<p>
|
||||
WorkAdventure allows you to create an online space to communicate spontaneously with others.
|
||||
And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
||||
</p>
|
||||
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Create your map</h3>
|
||||
<p>You can also create your own custom map by following the step of the documentation.</p>
|
||||
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
||||
</section>
|
||||
|
||||
<iframe title="contact"
|
||||
src="{$contactPageStore}"
|
||||
allow="clipboard-read; clipboard-write self {$contactPageStore}"
|
||||
allowfullscreen></iframe>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.create-map-main {
|
||||
height: calc(100% - 56px);
|
||||
|
||||
text-align: center;
|
||||
|
||||
section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
height: calc(100% - 56px);
|
||||
|
@ -1,38 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
function goToBuildingMap() {
|
||||
const sparkHost = "https://wiki.bstly.de/services/partey/map";
|
||||
window.open(sparkHost, "_blank");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="create-map-main">
|
||||
<section class="container-overflow">
|
||||
<section>
|
||||
<h3>Create your map</h3>
|
||||
<p>You can also create your own custom map by following the step of the documentation.</p>
|
||||
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.create-map-main {
|
||||
height: calc(100% - 56px);
|
||||
|
||||
text-align: center;
|
||||
|
||||
section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
75
front/src/Components/Menu/GuestSubMenu.svelte
Normal file
75
front/src/Components/Menu/GuestSubMenu.svelte
Normal file
@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
let HTMLShareLink: HTMLInputElement;
|
||||
|
||||
function copyLink() {
|
||||
HTMLShareLink.select();
|
||||
document.execCommand('copy');
|
||||
}
|
||||
|
||||
async function shareLink() {
|
||||
const shareData = {url: location.toString()};
|
||||
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
} catch (err) {
|
||||
console.error('Error: ' + err);
|
||||
copyLink();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="guest-main">
|
||||
<section class="container-overflow">
|
||||
<section class="share-url not-mobile">
|
||||
<h3>Share the link of the room !</h3>
|
||||
<input type="text" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
||||
</section>
|
||||
<section class="is-mobile">
|
||||
<h3>Share the link of the room !</h3>
|
||||
<input type="hidden" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.guest-main {
|
||||
height: calc(100% - 56px);
|
||||
|
||||
text-align: center;
|
||||
|
||||
section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
section.is-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px), only screen and (max-height: 600px) {
|
||||
div.guest-main {
|
||||
section.share-url.not-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section.is-mobile {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: calc(100% - 120px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -3,11 +3,11 @@
|
||||
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
||||
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
||||
import WorldsSubMenu from "./WorldsSubMenu.svelte";
|
||||
import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
|
||||
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
||||
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
||||
import ContactSubMenu from "./ContactSubMenu.svelte";
|
||||
import CustomSubMenu from "./CustomSubMenu.svelte"
|
||||
import GuestSubMenu from "./GuestSubMenu.svelte";
|
||||
import {
|
||||
checkSubMenuToShow,
|
||||
customMenuIframe,
|
||||
@ -20,21 +20,21 @@
|
||||
import type {Unsubscriber} from "svelte/store";
|
||||
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||
|
||||
let activeSubMenu: string = SubMenusInterface.settings;
|
||||
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
|
||||
let activeSubMenu: string = SubMenusInterface.profile;
|
||||
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
||||
let props: { url: string, allowApi: boolean };
|
||||
let unsubscriberSubMenuStore: Unsubscriber;
|
||||
|
||||
onMount(() => {
|
||||
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||
if(!get(subMenusStore).includes(activeSubMenu)) {
|
||||
switchMenu(SubMenusInterface.settings);
|
||||
switchMenu(SubMenusInterface.profile);
|
||||
}
|
||||
})
|
||||
|
||||
checkSubMenuToShow();
|
||||
|
||||
switchMenu(SubMenusInterface.settings);
|
||||
switchMenu(SubMenusInterface.profile);
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
@ -56,8 +56,8 @@
|
||||
case SubMenusInterface.worlds:
|
||||
activeComponent = WorldsSubMenu;
|
||||
break;
|
||||
case SubMenusInterface.createMap:
|
||||
activeComponent = CreateMapSubMenu;
|
||||
case SubMenusInterface.invite:
|
||||
activeComponent = GuestSubMenu;
|
||||
break;
|
||||
case SubMenusInterface.aboutRoom:
|
||||
activeComponent = AboutRoomSubMenu;
|
||||
|
@ -30,7 +30,6 @@
|
||||
z-index: 90;
|
||||
position: relative;
|
||||
margin: 25px;
|
||||
|
||||
img {
|
||||
pointer-events: auto;
|
||||
width: 24px;
|
||||
@ -38,8 +37,7 @@
|
||||
margin: 3px
|
||||
}
|
||||
}
|
||||
|
||||
.menuIcon img:hover {
|
||||
.menuIcon img:hover{
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
|
@ -1,60 +1,88 @@
|
||||
<script lang="typescript">
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
|
||||
import { menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected } from "../../Stores/MenuStore";
|
||||
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
|
||||
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
||||
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";
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
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 {PROFILE_URL} from "../../Enum/EnvironmentVariable";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {enableCameraSceneVisibilityStore} from "../../Stores/MediaStore";
|
||||
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
|
||||
import btnProfileSubMenuIdentity from "../images/btn-menu-profile-identity.svg";
|
||||
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
||||
import btnProfileSubMenuWoka from "../images/btn-menu-profile-woka.svg";
|
||||
|
||||
|
||||
function disableMenuStores() {
|
||||
function disableMenuStores(){
|
||||
menuVisiblilityStore.set(false);
|
||||
menuIconVisiblilityStore.set(false);
|
||||
}
|
||||
|
||||
function openEditCompanionScene() {
|
||||
function openEditCompanionScene(){
|
||||
disableMenuStores();
|
||||
selectCompanionSceneVisibleStore.set(true);
|
||||
gameManager.leaveGame(SelectCompanionSceneName, new SelectCompanionScene());
|
||||
gameManager.leaveGame(SelectCompanionSceneName,new SelectCompanionScene());
|
||||
}
|
||||
|
||||
function openEditSkinScene() {
|
||||
function openEditNameScene(){
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
gameManager.leaveGame(LoginSceneName,new LoginScene());
|
||||
}
|
||||
|
||||
function openEditSkinScene(){
|
||||
disableMenuStores();
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
||||
gameManager.leaveGame(SelectCharacterSceneName,new SelectCharacterScene());
|
||||
}
|
||||
|
||||
function logOut() {
|
||||
function logOut(){
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
connectionManager.logout();
|
||||
}
|
||||
|
||||
function getProfileUrl() {
|
||||
function getProfileUrl(){
|
||||
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
|
||||
}
|
||||
|
||||
function openEnableCameraScene() {
|
||||
function openEnableCameraScene(){
|
||||
disableMenuStores();
|
||||
enableCameraSceneVisibilityStore.showEnableCameraScene();
|
||||
gameManager.leaveGame(EnableCameraSceneName, new EnableCameraScene());
|
||||
gameManager.leaveGame(EnableCameraSceneName,new EnableCameraScene());
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="customize-main">
|
||||
<div class="submenu">
|
||||
<section>
|
||||
<button type="button" class="nes-btn" 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={openEnableCameraScene}>Setup camera</button>
|
||||
<!--
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
|
||||
<img src={btnProfileSubMenuIdentity} alt="Edit your name">
|
||||
<span class="btn-hover">Edit your name</span>
|
||||
</button>
|
||||
-->
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
|
||||
<img src={btnProfileSubMenuWoka} alt="Edit your Avatar">
|
||||
<span class="btn-hover">Edit your Avatar</span>
|
||||
</button>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>
|
||||
<img src={btnProfileSubMenuCompanion} alt="Change your companion">
|
||||
<span class="btn-hover">Change your companion</span>
|
||||
</button>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>
|
||||
<img src={btnProfileSubMenuCamera} alt="Edit your camera">
|
||||
<span class="btn-hover">Edit your camera</span>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{#if $userIsConnected}
|
||||
<section>
|
||||
{#if PROFILE_URL != undefined}
|
||||
@ -69,13 +97,48 @@
|
||||
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.customize-main {
|
||||
overflow-y: auto;
|
||||
height: 90%;
|
||||
div.customize-main{
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
|
||||
div.submenu{
|
||||
height: 100%;
|
||||
width: 50px;
|
||||
|
||||
button {
|
||||
transition: all .5s ease;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 10px;
|
||||
max-height: 44px;
|
||||
|
||||
img {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span.btn-hover{
|
||||
display: none;
|
||||
font-family: "Press Start 2P";
|
||||
}
|
||||
|
||||
&:hover{
|
||||
width: auto;
|
||||
|
||||
span.btn-hover {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.content {
|
||||
width: 100%;
|
||||
section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -95,9 +158,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
div.customize-main section button {
|
||||
div.customize-main.content section button {
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script lang="typescript">
|
||||
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
||||
import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||
import {localStreamStore, isSilentStore} from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import { srcObject } from "./Video/utils";
|
||||
import {onDestroy} from "svelte";
|
||||
import {srcObject} from "./Video/utils";
|
||||
|
||||
let stream: MediaStream | null;
|
||||
let stream : MediaStream|null;
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
|
1
front/src/Components/images/btn-menu-profile-camera.svg
Normal file
1
front/src/Components/images/btn-menu-profile-camera.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><g id="btn_setup_camera" data-name="btn setup camera"><circle cx="24.71" cy="13.11" r="1.46"/><path d="M8.65,23.34H40.78V2.89H31.16L28.24,0h-7L18.27,2.89H8.65ZM32,8.73h2.92v2.92H32Zm-7.31,0a4.39,4.39,0,1,1-4.38,4.38A4.39,4.39,0,0,1,24.71,8.73Z"/><path d="M2.81,44H5.73v5.84h8.76V44h5.84V46.9h2.92V44h5.84V46.9H32V44h5.84V46.9h2.92V44h5.84V41.06h-33l-3.52-3.53L6.58,41.06H2.81Z"/><path d="M2.81,32.3H8.65v2.92h2.92V32.3h5.84v2.92h2.92V32.3h5.84v2.92h2.92V32.3h5.85v5.84H43.7V32.3h2.92V29.37H42.84l-3.52-3.52-3.53,3.52h-33Z"/></g></svg>
|
After Width: | Height: | Size: 629 B |
@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50"><g id="btn_setup_companion" data-name="btn setup companion"><g id="iNhyGC.tif"><image id="Layer_0" data-name="Layer 0" width="15" height="30" transform="translate(13.21 0.25) scale(1.65)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAfCAYAAADupU20AAAACXBIWXMAAAbEAAAGxAGo1xHEAAAB20lEQVQ4T51UO04DMRScRYmEhGWlApRsBcUap4VUXICSjpqaY3ACSgpOgERQKnpoNjXRhjrkANZGikRhiuDH8w8BI7l5n/H4fVxYaxFC66FnnM1eiyjoC53QoPXQjkYnkS1HUnAFWg+tUhXatsVi8Q4AUKqClBJ1PU0q8RTMZq+Fk++CU4o4oiekbjHGhCbCVtbzS/xIwGuSQ/GbNgL5VkYEWg9tWQ4ghCCb60qKJGqjS5ZSkt0YkyWhGjjZYTIASClJUfg8r4hKVWiaOYwx0WmaOZSqEKIDfEu/PDvGHYCmmUeBSlW4PDvGTdt6o00Krq8uAIDkl+UASlUoy4Fnd3EOHXc7AOzu7QN4o/l3ie79G/+G3Kkojo50svI5hB3pAOnK5xDG0TLV9ZSMqe3jft4NKuLt/Qu6qzXG40m0fcYYjMcTdFdr3N6/eL4tYDOqhwc9AMDT4wNScPbDg563XFTEfr8P+3C6MZ4/RwTct1wuqYiFtTa5A3U9RXe1xsfONtUktRNUxMXi3Rua0egExhgi5MkcRFCWA3I6IgD0Xu7jJEQghKDE8BaX6OI4vE9VCEE38r3nqx7CW2c+9yFy00oE/x1lInDTlyPi3eCgP5F/VanPM+dPfut/wScEgxZt/KHdLgAAAABJRU5ErkJggg=="/></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><g id="btn_setup_identity" data-name="btn setup identity"><path d="M31.94,6l-4.35,6.39a3.63,3.63,0,1,1-3.07-1.7,3.71,3.71,0,0,1,.67.06l4-5.89a16,16,0,0,0-9.37,0L17,.76a1.45,1.45,0,0,0-2.4,1.64L17.09,6A16,16,0,0,0,8.56,20.15V33.69a16,16,0,0,0,31.91,0V20.15A16,16,0,0,0,31.94,6Zm-.65,32.49H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,0,1,0,2.9Zm0-5.8H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,1,1,0,2.9Zm0-5.8H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,1,1,0,2.9Z"/><path d="M34.42,2.4A1.45,1.45,0,1,0,32,.76L29.21,4.89A16.38,16.38,0,0,1,31.94,6Z"/></g></svg>
|
After Width: | Height: | Size: 661 B |
1
front/src/Components/images/btn-menu-profile-woka.svg
Normal file
1
front/src/Components/images/btn-menu-profile-woka.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50"><g id="btn_setup_woka" data-name="btn setup woka"><g id="NP8bMB.tif"><image id="Layer_0" data-name="Layer 0" width="23" height="29" transform="translate(4.61 0.1) scale(1.71)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAdCAYAAABBsffGAAAACXBIWXMAAAZ1AAAGdQGEn07tAAACCUlEQVRIS61WS4rDMAx9abLoxuqi4MIYSk+Q+59iBnqCUlBhDNnYmy4aMosgj+3YyRTmQReVpSdZP6eZpglrMOajqsD8aGpnANDUyIW0bTsAwPF4DGfDMAAAxvEFoO6kSG7Mx9S2XSBUSi10vPcAZkfj+Co6WJDHxCXSHN77qoOuZJATE9FCxzkH4PdW1n4vdHbxH4k6Rom4JG/bblH8hBxIo64RC+RcKZUUXLAgF2wRC9b0AvlaP7+DmGcnAq1PdYs3oPUpOKim5T+wA+YJk1aS6XsXYmftd+j3EPnWnvgrYp4kLcyPZhxf8N6HIdmCcw7e+79PaDxta60mxKXpBLLdEk/oOL6wtmPinRLbxNEHciHu+34zohhan6CUAhHher0mDpK0xMQSrTEGAJIaSKqYGUqpsH77vsfX12fQ62aCOeo8YmMMDodDUHbOgYgSGTMnN5QFxvxoQrdcLhcA633+fD6rZ2InPADQSdREBGYOT1fuxFqL/X4Pa22QMXPy5HnvYYwJ0e+AOVc5oXSJcw7OOWitQUTQWgdZvmrFXvhCWpxzSaWHYQiFk6GSnxSQiAKhDGBc+I750cgWK60AIsLtdlukSbooRs5V/bQ4n8+TpCbveeltGaT7/V7cS9WVK4WVFADpzUQueiWs7nOlFIwxi/GvyXOskm+9o1vn1ZwD6XsYp6Qmz/ED0qpw9h1b2uQAAAAASUVORK5CYII="/></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -42,20 +42,11 @@ class ConnectionManager {
|
||||
const nonce = localUserStore.generateNonce();
|
||||
localUserStore.setAuthToken(null);
|
||||
|
||||
//TODO fix me to redirect this URL by pusher
|
||||
if (!this._currentRoom) {
|
||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
// also allow OIDC login without admin API by using pusher
|
||||
let redirectUrl : URL;
|
||||
if (this._currentRoom.iframeAuthentication) {
|
||||
redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`);
|
||||
} else {
|
||||
// need origin if PUSHER_URL is relative (in Single-Domain-Deployment)
|
||||
redirectUrl = new URL(`${PUSHER_URL}/login-screen`, (!PUSHER_URL.startsWith('http:') || !PUSHER_URL.startsWith('https:')) ? window.location.origin : undefined);
|
||||
}
|
||||
const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`);
|
||||
redirectUrl.searchParams.append("state", state);
|
||||
redirectUrl.searchParams.append("nonce", nonce);
|
||||
redirectUrl.searchParams.append("playUri", this._currentRoom.key);
|
||||
@ -88,6 +79,16 @@ class ConnectionManager {
|
||||
const connexionType = urlManager.getGameConnexionType();
|
||||
this.connexionType = connexionType;
|
||||
this._currentRoom = null;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get("token");
|
||||
if (token) {
|
||||
this.authToken = token;
|
||||
localUserStore.setAuthToken(token);
|
||||
//token was saved, clear url
|
||||
urlParams.delete("token");
|
||||
}
|
||||
|
||||
if (connexionType === GameConnexionTypes.login) {
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
if (this.loadOpenIDScreen() !== null) {
|
||||
@ -96,6 +97,8 @@ class ConnectionManager {
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (!token) {
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
@ -105,6 +108,8 @@ class ConnectionManager {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
localUserStore.setCode(code);
|
||||
}
|
||||
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
@ -173,14 +178,20 @@ class ConnectionManager {
|
||||
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||
|
||||
//defined last room url this room path
|
||||
localUserStore.setLastRoomUrl(this._currentRoom.key);
|
||||
|
||||
//todo: add here some kind of warning if authToken has expired.
|
||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||
await this.anonymousLogin();
|
||||
} else {
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
analyticsClient.loggedWithSso();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadOpenIDScreen();
|
||||
return Promise.reject(new Error("You will be redirect on login page"));
|
||||
}
|
||||
}
|
||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||
@ -208,13 +219,13 @@ class ConnectionManager {
|
||||
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
|
||||
}
|
||||
|
||||
//clean history with new URL
|
||||
window.history.pushState({}, document.title, window.location.pathname);
|
||||
this.serviceWorker = new _ServiceWorker();
|
||||
return Promise.resolve(this._currentRoom);
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
localUserStore.setAuthToken(null);
|
||||
try {
|
||||
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
||||
this.localUser = new LocalUser(data.userUuid, []);
|
||||
this.authToken = data.authToken;
|
||||
@ -223,9 +234,6 @@ class ConnectionManager {
|
||||
localUserStore.saveUser(this.localUser);
|
||||
localUserStore.setAuthToken(this.authToken);
|
||||
}
|
||||
} catch (error) {
|
||||
this.loadOpenIDScreen();
|
||||
}
|
||||
}
|
||||
|
||||
public initBenchmark(): void {
|
||||
@ -293,33 +301,30 @@ class ConnectionManager {
|
||||
//set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
const token = localUserStore.getAuthToken();
|
||||
const state = localUserStore.getState();
|
||||
const code = localUserStore.getCode();
|
||||
const nonce = localUserStore.getNonce();
|
||||
|
||||
if (!token) {
|
||||
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, username, userUuid } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
|
||||
this.localUser = new LocalUser(userUuid, []);
|
||||
this.authToken = authToken;
|
||||
}
|
||||
const { authToken, userUuid, textures, email, username } = await Axios.get(`${PUSHER_URL}/login-callback`, {
|
||||
params: { code, nonce, token },
|
||||
}).then((res) => res.data);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.localUser = new LocalUser(userUuid, textures, email);
|
||||
localUserStore.saveUser(this.localUser);
|
||||
localUserStore.setAuthToken(this.authToken);
|
||||
|
||||
this.authToken = authToken;
|
||||
gameManager.setPlayerName(username);
|
||||
|
||||
//user connected, set connected store for menu at true
|
||||
userIsConnected.set(true);
|
||||
|
||||
if (this.connexionType === GameConnexionTypes.anonymous) {
|
||||
this.connexionType = GameConnexionTypes.empty
|
||||
}
|
||||
}
|
||||
|
||||
async getWorlds() {
|
||||
|
@ -120,19 +120,12 @@ class LocalUserStore {
|
||||
return localStorage.getItem(fullscreenKey) === "true";
|
||||
}
|
||||
|
||||
setLastRoomUrl(roomUrl: string | null): void {
|
||||
if (roomUrl) {
|
||||
setLastRoomUrl(roomUrl: string): void {
|
||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||
caches.open(cacheAPIIndex).then((cache) => {
|
||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem(lastRoomUrl);
|
||||
caches.open(cacheAPIIndex).then((cache) => {
|
||||
cache.delete(`/${lastRoomUrl}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
getLastRoomUrl(): string {
|
||||
return (
|
||||
@ -172,8 +165,15 @@ class LocalUserStore {
|
||||
|
||||
verifyState(value: string): boolean {
|
||||
const oldValue = localStorage.getItem(state);
|
||||
if (!oldValue) {
|
||||
localStorage.setItem(state, value);
|
||||
return true;
|
||||
}
|
||||
return oldValue === value;
|
||||
}
|
||||
setState(value: string) {
|
||||
localStorage.setItem(state, value);
|
||||
}
|
||||
getState(): string | null {
|
||||
return localStorage.getItem(state);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Axios from "axios";
|
||||
import { CONTACT_URL, PUSHER_URL } from "../Enum/EnvironmentVariable";
|
||||
import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||
import type { CharacterTexture } from "./LocalUser";
|
||||
import { localUserStore } from "./LocalUserStore";
|
||||
|
||||
@ -14,8 +14,8 @@ export interface RoomRedirect {
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private _authenticationMandatory: boolean = false;
|
||||
private _iframeAuthentication?: string;
|
||||
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS as boolean;
|
||||
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
private instance: string | undefined;
|
||||
@ -106,8 +106,8 @@ export class Room {
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._group = data.group;
|
||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
||||
this._iframeAuthentication = data.iframeAuthentication;
|
||||
this._authenticationMandatory = data.authenticationMandatory || (DISABLE_ANONYMOUS as boolean);
|
||||
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
|
||||
this._contactPage = data.contactPage || CONTACT_URL;
|
||||
return new MapDetail(data.mapUrl, data.textures);
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
||||
export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
||||
export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false;
|
||||
export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
|
||||
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
|
||||
|
@ -66,7 +66,7 @@ export abstract class Character extends Container {
|
||||
this.playAnimation(direction, moving);
|
||||
})
|
||||
.catch(() => {
|
||||
return lazyLoadPlayerCharacterTextures(scene.load, [ "color_22", "eyes_23" ]).then((textures) => {
|
||||
return lazyLoadPlayerCharacterTextures(scene.load, ["color_22", "eyes_23"]).then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
@ -77,7 +77,7 @@ export abstract class Character extends Container {
|
||||
fontFamily: '"Press Start 2P"',
|
||||
fontSize: "8px",
|
||||
strokeThickness: 2,
|
||||
stroke: "#000",
|
||||
stroke: "gray",
|
||||
});
|
||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerName);
|
||||
@ -155,56 +155,56 @@ export abstract class Character extends Container {
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [ 0, 1, 2, 1 ],
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [ 3, 4, 5, 4 ],
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [ 6, 7, 8, 7 ],
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [ 9, 10, 11, 10 ],
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [ 1 ],
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [ 4 ],
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [ 7 ],
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [ 10 ],
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1,
|
||||
},
|
||||
@ -213,7 +213,7 @@ export abstract class Character extends Container {
|
||||
|
||||
protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
|
||||
if (this.invisible) return;
|
||||
for (const [ texture, sprite ] of this.sprites.entries()) {
|
||||
for (const [texture, sprite] of this.sprites.entries()) {
|
||||
if (!sprite.anims) {
|
||||
console.error("ANIMS IS NOT DEFINED!!!");
|
||||
return;
|
||||
|
@ -1,7 +1,7 @@
|
||||
//The list of all the player textures, both the default models and the partial textures used for customization
|
||||
|
||||
export interface BodyResourceDescriptionListInterface {
|
||||
[ key: string ]: BodyResourceDescriptionInterface;
|
||||
[key: string]: BodyResourceDescriptionInterface;
|
||||
}
|
||||
|
||||
export interface BodyResourceDescriptionInterface {
|
||||
@ -434,10 +434,7 @@ export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
name: "accessory_mate_bottle",
|
||||
img: "resources/customisation/character_accessories/mate_bottle1.png",
|
||||
},
|
||||
accessory_mask: {
|
||||
name: "accessory_mask",
|
||||
img: "resources/customisation/character_accessories/mask.png",
|
||||
},
|
||||
accessory_mask: { name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png" },
|
||||
wheelchair: {
|
||||
name: "accessory_wheelchair",
|
||||
img: "resources/customisation/character_accessories/wheelchair.png",
|
||||
|
@ -109,6 +109,11 @@ export class GameMapPropertiesListener {
|
||||
return;
|
||||
}
|
||||
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
coWebsite: undefined,
|
||||
state: OpenCoWebsiteState.LOADING,
|
||||
});
|
||||
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager.loadCoWebsite(
|
||||
openWebsiteProperty as string,
|
||||
|
@ -89,8 +89,6 @@ import { get } from "svelte/store";
|
||||
import { contactPageStore } from "../../Stores/MenuStore";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
|
||||
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
reconnecting: boolean;
|
||||
@ -271,7 +269,7 @@ export class GameScene extends DirtyScene {
|
||||
// So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes)
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure
|
||||
const url = new URL(file.src);
|
||||
const host = url.host.split(":")[ 0 ];
|
||||
const host = url.host.split(":")[0];
|
||||
if (
|
||||
window.location.protocol === "https:" &&
|
||||
file.src === this.MapUrlFile &&
|
||||
@ -326,8 +324,8 @@ export class GameScene extends DirtyScene {
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(this.load as any).rexWebFont({
|
||||
custom: {
|
||||
families: [ "Press Start 2P" ],
|
||||
urls: [ "/resources/fonts/fonts.css" ],
|
||||
families: ["Press Start 2P"],
|
||||
urls: ["/resources/fonts/fonts.css"],
|
||||
testString: "abcdefg",
|
||||
},
|
||||
});
|
||||
@ -373,7 +371,7 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
for (const [ itemType, objectsOfType ] of this.objectsByType) {
|
||||
for (const [itemType, objectsOfType] of this.objectsByType) {
|
||||
// FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin.
|
||||
|
||||
let itemFactory: ItemFactoryInterface;
|
||||
@ -404,7 +402,7 @@ export class GameScene extends DirtyScene {
|
||||
// TODO: we should pass here a factory to create sprites (maybe?)
|
||||
|
||||
// Do we have a state for this object?
|
||||
const state = roomJoinedAnswer.items[ object.id ];
|
||||
const state = roomJoinedAnswer.items[object.id];
|
||||
|
||||
const actionableItem = itemFactory.factory(this, object, state);
|
||||
this.actionableItems.set(actionableItem.getId(), actionableItem);
|
||||
@ -634,7 +632,7 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all([ this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises ]).then(() => {
|
||||
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises]).then(() => {
|
||||
this.scene.wake();
|
||||
});
|
||||
}
|
||||
@ -943,7 +941,8 @@ export class GameScene extends DirtyScene {
|
||||
html += `<input id="popupinput-${openPopupEvent.popupId}" class="nes-input" />`
|
||||
}
|
||||
html += `</div>`;
|
||||
const buttonContainer = `<div class="buttonContainer"</div>`;
|
||||
|
||||
const buttonContainer = '<div class="buttonContainer"</div>';
|
||||
html += buttonContainer;
|
||||
let id = 0;
|
||||
for (const button of openPopupEvent.buttons) {
|
||||
@ -1190,7 +1189,7 @@ export class GameScene extends DirtyScene {
|
||||
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 ];
|
||||
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;
|
||||
@ -1296,8 +1295,8 @@ export class GameScene extends DirtyScene {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < phaserLayers.length; i++) {
|
||||
phaserLayers[ i ].setVisible(visible);
|
||||
phaserLayers[ i ].setCollisionByProperty({ collides: true }, visible);
|
||||
phaserLayers[i].setVisible(visible);
|
||||
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
|
||||
}
|
||||
}
|
||||
this.markDirty();
|
||||
|
@ -17,7 +17,7 @@ export class LoginScene extends ResizableScene {
|
||||
this.name = gameManager.getPlayerName() || "";
|
||||
}
|
||||
|
||||
preload() { }
|
||||
preload() {}
|
||||
|
||||
create() {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
@ -28,9 +28,9 @@ export class LoginScene extends ResizableScene {
|
||||
gameManager.currentStartedRoom.authenticationMandatory
|
||||
) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
} else {
|
||||
loginSceneVisibleStore.set(true);
|
||||
loginSceneVisibleIframeStore.set(true);
|
||||
}
|
||||
loginSceneVisibleStore.set(true);
|
||||
}
|
||||
|
||||
public login(name: string): void {
|
||||
@ -43,7 +43,7 @@ export class LoginScene extends ResizableScene {
|
||||
loginSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void { }
|
||||
update(time: number, delta: number): void {}
|
||||
|
||||
public onResize(): void { }
|
||||
public onResize(): void {}
|
||||
}
|
||||
|
@ -67,6 +67,9 @@ function createChatMessagesStore() {
|
||||
});
|
||||
},
|
||||
addPersonnalMessage(text: string) {
|
||||
//post message iframe listener
|
||||
iframeListener.sendUserInputChat(text);
|
||||
|
||||
newChatMessageStore.set(text);
|
||||
update((list) => {
|
||||
const lastMessage = list[list.length - 1];
|
||||
|
@ -35,21 +35,21 @@ export enum SubMenusInterface {
|
||||
settings = "Settings",
|
||||
profile = "Profile",
|
||||
worlds = "Worlds",
|
||||
createMap = "Create a Map",
|
||||
aboutRoom = "About the Room",
|
||||
invite = "Invite",
|
||||
aboutRoom = "Credit",
|
||||
globalMessages = "Global Messages",
|
||||
contact = "Contact",
|
||||
}
|
||||
|
||||
function createSubMenusStore() {
|
||||
const { subscribe, update } = writable<string[]>([
|
||||
SubMenusInterface.settings,
|
||||
SubMenusInterface.profile,
|
||||
SubMenusInterface.worlds,
|
||||
SubMenusInterface.createMap,
|
||||
SubMenusInterface.aboutRoom,
|
||||
SubMenusInterface.globalMessages,
|
||||
SubMenusInterface.contact,
|
||||
SubMenusInterface.settings,
|
||||
SubMenusInterface.invite,
|
||||
SubMenusInterface.aboutRoom,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -517,6 +517,7 @@ class CoWebsiteManager {
|
||||
};
|
||||
|
||||
this.coWebsites.push(coWebsite);
|
||||
// this.cowebsiteSubIconsDom.appendChild(icon);
|
||||
|
||||
const onTimeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
@ -564,6 +565,10 @@ class CoWebsiteManager {
|
||||
this.fire();
|
||||
}
|
||||
|
||||
if (coWebsite) {
|
||||
iframeListener.unregisterIframe(coWebsite.iframe);
|
||||
}
|
||||
|
||||
this.removeCoWebsiteFromStack(coWebsite);
|
||||
resolve();
|
||||
})
|
||||
|
@ -147,7 +147,7 @@ const wa = {
|
||||
* @deprecated Use WA.ui.openPopup instead
|
||||
*/
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[], popupClass : string, input : boolean): Popup {
|
||||
console.warn('Method WA.openPopup is deprecated. Please use WA.ui.openPopup instead');
|
||||
console.warn("Method WA.openPopup is deprecated. Please use WA.ui.openPopup instead");
|
||||
return ui.openPopup(targetObject, message, buttons, popupClass, input);
|
||||
},
|
||||
/**
|
||||
|
@ -21,7 +21,7 @@
|
||||
}
|
||||
|
||||
aside {
|
||||
background: #212529;
|
||||
background: gray;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,8 +1,9 @@
|
||||
@import "~nes.css/css/nes.min.css";
|
||||
@import "nes.custom.css";
|
||||
@import "quill.snow.css";
|
||||
@import "cowebsite.scss";
|
||||
@import "cowebsite-mobile.scss";
|
||||
@import "style";
|
||||
@import "mobile-style.scss";
|
||||
@import "fonts.scss";
|
||||
@import "quill.snow.css";
|
||||
@import "TextGlobalMessageSvelte-Style";
|
||||
|
@ -8,8 +8,7 @@ body img:focus,
|
||||
body input:focus {
|
||||
outline: -webkit-focus-ring-color auto 0;
|
||||
}
|
||||
|
||||
body .message-info {
|
||||
body .message-info{
|
||||
width: 20%;
|
||||
height: auto;
|
||||
min-height: 30px;
|
||||
@ -19,20 +18,17 @@ body .message-info {
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body .message-info.error {
|
||||
body .message-info.error{
|
||||
background: red;
|
||||
}
|
||||
|
||||
body .message-info.success {
|
||||
body .message-info.success{
|
||||
background: green;
|
||||
}
|
||||
|
||||
body .message-info.info {
|
||||
body .message-info.info{
|
||||
background: dodgerblue;
|
||||
}
|
||||
|
||||
body .message-info.warning {
|
||||
body .message-info.warning{
|
||||
background: #ffa500d6;
|
||||
}
|
||||
|
||||
@ -161,18 +157,16 @@ video.myCamVideo{
|
||||
/*height: 113px;*/
|
||||
}
|
||||
|
||||
.sound-progress {
|
||||
.sound-progress{
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
top: calc(50% - 5px);
|
||||
}
|
||||
|
||||
.sound-progress.active {
|
||||
.sound-progress.active{
|
||||
display: table-column;
|
||||
}
|
||||
|
||||
.sound-progress span {
|
||||
.sound-progress span{
|
||||
position: absolute;
|
||||
color: black;
|
||||
background-color: #00000020;
|
||||
@ -180,28 +174,22 @@ video.myCamVideo{
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.sound-progress span.active {
|
||||
.sound-progress span.active{
|
||||
background-color: #00c3ff66
|
||||
}
|
||||
|
||||
.sound-progress span:nth-child(1) {
|
||||
.sound-progress span:nth-child(1){
|
||||
top: calc(50% + 20px);
|
||||
}
|
||||
|
||||
.sound-progress span:nth-child(2) {
|
||||
.sound-progress span:nth-child(2){
|
||||
top: calc(50% + 10px);
|
||||
}
|
||||
|
||||
.sound-progress span:nth-child(3) {
|
||||
.sound-progress span:nth-child(3){
|
||||
top: calc(50% - 0px);
|
||||
}
|
||||
|
||||
.sound-progress span:nth-child(4) {
|
||||
.sound-progress span:nth-child(4){
|
||||
top: calc(50% - 10px);
|
||||
}
|
||||
|
||||
.sound-progress span:nth-child(5) {
|
||||
.sound-progress span:nth-child(5){
|
||||
top: calc(50% - 20px);
|
||||
}
|
||||
|
||||
@ -232,39 +220,32 @@ video.myCamVideo{
|
||||
transition-timing-function: ease-in-out;
|
||||
margin: 0 4%;
|
||||
}
|
||||
|
||||
.btn-cam-action div.disabled {
|
||||
background: #d75555;
|
||||
}
|
||||
|
||||
.btn-cam-action div.enabled {
|
||||
background: #73c973;
|
||||
}
|
||||
|
||||
.btn-cam-action:hover div {
|
||||
.btn-cam-action:hover div{
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-cam-action div:hover {
|
||||
.btn-cam-action div:hover{
|
||||
background: #407cf7;
|
||||
box-shadow: 4px 4px 48px #666;
|
||||
transition: 120ms;
|
||||
}
|
||||
|
||||
.btn-micro {
|
||||
pointer-events: none;
|
||||
.btn-micro{
|
||||
pointer-events: auto;
|
||||
transition: all .3s;
|
||||
/*right: 44px;*/
|
||||
}
|
||||
|
||||
.btn-video {
|
||||
pointer-events: none;
|
||||
.btn-video{
|
||||
pointer-events: auto;
|
||||
transition: all .25s;
|
||||
/*right: 134px;*/
|
||||
}
|
||||
|
||||
.btn-monitor {
|
||||
pointer-events: none;
|
||||
.btn-monitor{
|
||||
pointer-events: auto;
|
||||
transition: all .2s;
|
||||
/*right: 224px;*/
|
||||
}
|
||||
@ -290,26 +271,24 @@ video.myCamVideo{
|
||||
right: 44px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-cam-action div img {
|
||||
.btn-cam-action div img{
|
||||
height: 22px;
|
||||
width: 30px;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
|
||||
/* Spinner */
|
||||
|
||||
.connecting-spinner {
|
||||
/*display: inline-block;*/
|
||||
position: absolute;
|
||||
left: calc(50% - 62px);
|
||||
top: calc(50% - 62px);
|
||||
|
||||
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
}
|
||||
.connecting-spinner:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
@ -321,7 +300,6 @@ video.myCamVideo{
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: connecting-spinner 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes connecting-spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@ -331,14 +309,16 @@ video.myCamVideo{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.rtc-error {
|
||||
position: absolute;
|
||||
left: calc(50% - 68px);
|
||||
top: calc(50% - 68px);
|
||||
|
||||
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.rtc-error:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
@ -357,13 +337,11 @@ video.myCamVideo{
|
||||
}
|
||||
|
||||
/* New layout */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@ -374,16 +352,20 @@ body {
|
||||
.game-overlay {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
flex-direction: column;
|
||||
}
|
||||
.sidebar>div {
|
||||
|
||||
.sidebar > div {
|
||||
max-height: 21%;
|
||||
height: 21%;
|
||||
}
|
||||
.sidebar>div:hover {
|
||||
|
||||
.sidebar > div:hover {
|
||||
max-height: 25%;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#game {
|
||||
@ -398,7 +380,6 @@ body {
|
||||
* Style Input Range
|
||||
* https://www.cssportal.com/style-input-range/
|
||||
*/
|
||||
|
||||
input[type=range] {
|
||||
height: 28px;
|
||||
-webkit-appearance: none;
|
||||
@ -406,11 +387,9 @@ input[type=range] {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
@ -420,7 +399,6 @@ input[type=range]::-webkit-slider-runnable-track {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
box-shadow: 1px 1px 1px #000000;
|
||||
border: 1px solid #000000;
|
||||
@ -431,11 +409,9 @@ input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
margin-top: -8.5px;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
@ -445,7 +421,6 @@ input[type=range]::-moz-range-track {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
input[type=range]::-moz-range-thumb {
|
||||
box-shadow: 1px 1px 1px #000000;
|
||||
border: 1px solid #000000;
|
||||
@ -454,7 +429,6 @@ input[type=range]::-moz-range-thumb {
|
||||
border-radius: 5px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-track {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
@ -463,21 +437,18 @@ input[type=range]::-ms-track {
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-fill-lower {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 10px;
|
||||
box-shadow: 1px 1px 1px #000000;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-fill-upper {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 10px;
|
||||
box-shadow: 1px 1px 1px #000000;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-thumb {
|
||||
margin-top: 1px;
|
||||
box-shadow: 1px 1px 1px #000000;
|
||||
@ -487,15 +458,14 @@ input[type=range]::-ms-thumb {
|
||||
border-radius: 5px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-ms-fill-lower {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
.game-overlay {
|
||||
display: none;
|
||||
position: absolute;
|
||||
@ -505,11 +475,11 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
/* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */
|
||||
}
|
||||
|
||||
.game-overlay+div {
|
||||
.game-overlay + div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.game-overlay+div>div {
|
||||
.game-overlay + div > div {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@ -529,7 +499,7 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.main-section>div {
|
||||
.main-section > div {
|
||||
margin: 2%;
|
||||
flex-basis: 96%;
|
||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s;
|
||||
@ -537,7 +507,7 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
/*flex-shrink: 2;*/
|
||||
}
|
||||
|
||||
.main-section>div:hover {
|
||||
.main-section > div:hover {
|
||||
margin: 0%;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
@ -547,7 +517,7 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar>div {
|
||||
.sidebar > div {
|
||||
margin: 2%;
|
||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
|
||||
pointer-events: auto;
|
||||
@ -557,12 +527,12 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar>div:hover {
|
||||
.sidebar > div:hover {
|
||||
margin: 0%;
|
||||
}
|
||||
|
||||
/* Let's make sure videos are vertically centered if they need to be cropped */
|
||||
|
||||
/* Let's make sure videos are vertically centered if they need to be cropped */
|
||||
.media-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -572,20 +542,21 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
.chat-mode {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
align-items: flex-start;
|
||||
|
||||
padding: 1%;
|
||||
}
|
||||
|
||||
.chat-mode>div {
|
||||
.chat-mode > div {
|
||||
margin: 1%;
|
||||
max-height: 96%;
|
||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s;
|
||||
}
|
||||
|
||||
.chat-mode>div:hover {
|
||||
.chat-mode > div:hover {
|
||||
margin: 0%;
|
||||
}
|
||||
|
||||
.chat-mode.one-col {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
@ -618,14 +589,14 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
border-radius: 0 0 15px 15px;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
.message-container{
|
||||
height: auto;
|
||||
border-radius: 0 0 10px 10px;
|
||||
color: white;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.message-container .content-message {
|
||||
.message-container .content-message{
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
@ -682,12 +653,10 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
|
||||
|
||||
/* VIDEO QUALITY */
|
||||
|
||||
.main-console div.setting h1 {
|
||||
.main-console div.setting h1{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.main-console div.setting select {
|
||||
.main-console div.setting select{
|
||||
background: black;
|
||||
color: white;
|
||||
min-width: 280px;
|
||||
@ -696,20 +665,17 @@ input[type=range]:focus::-ms-fill-upper {
|
||||
padding: 10px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.main-console div.setting select:focus {
|
||||
.main-console div.setting select:focus{
|
||||
border: solid 1px white;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.main-console div.setting.active section {
|
||||
.main-console div.setting.active section{
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*REPORT input*/
|
||||
|
||||
div.modal-report-user {
|
||||
div.modal-report-user{
|
||||
position: absolute;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
@ -719,7 +685,7 @@ div.modal-report-user {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.modal-report-user textarea {
|
||||
.modal-report-user textarea{
|
||||
position: absolute;
|
||||
height: 200px;
|
||||
z-index: 999;
|
||||
@ -731,7 +697,7 @@ div.modal-report-user {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.modal-report-user img {
|
||||
.modal-report-user img{
|
||||
position: absolute;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
@ -740,7 +706,7 @@ div.modal-report-user {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.modal-report-user img#cancel-report-user {
|
||||
.modal-report-user img#cancel-report-user{
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
right: 0;
|
||||
@ -751,7 +717,7 @@ div.modal-report-user {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.modal-report-user button {
|
||||
.modal-report-user button{
|
||||
position: absolute;
|
||||
top: 450px;
|
||||
left: calc(50% - 50px);
|
||||
@ -770,7 +736,7 @@ div.modal-report-user {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.modal-report-user p#title-report-user {
|
||||
.modal-report-user p#title-report-user{
|
||||
font-size: 30px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
@ -779,7 +745,7 @@ div.modal-report-user {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-report-user p#body-report-user {
|
||||
.modal-report-user p#body-report-user{
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
@ -787,29 +753,24 @@ div.modal-report-user {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 30px;
|
||||
max-width: calc(800px - 60px);
|
||||
/* size of modal - padding*/
|
||||
max-width: calc(800px - 60px); /* size of modal - padding*/
|
||||
}
|
||||
|
||||
|
||||
/*MESSAGE*/
|
||||
|
||||
.discussion {
|
||||
.discussion{
|
||||
position: fixed;
|
||||
left: -300px;
|
||||
top: 0px;
|
||||
width: 280px;
|
||||
width: 220px;
|
||||
height: 100%;
|
||||
background-color: #333333;
|
||||
padding: 20px;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.discussion.active {
|
||||
.discussion.active{
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.discussion .active-btn {
|
||||
.discussion .active-btn{
|
||||
display: none;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
@ -821,47 +782,43 @@ div.modal-report-user {
|
||||
border: none;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.discussion .active-btn.active {
|
||||
.discussion .active-btn.active{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.discussion .active-btn:hover {
|
||||
transform: scale(1.1) rotateY(3.142rad);
|
||||
}
|
||||
|
||||
.discussion .active-btn img {
|
||||
.discussion .active-btn img{
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: 13px 5px;
|
||||
}
|
||||
|
||||
.discussion .close-btn {
|
||||
.discussion .close-btn{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.discussion .close-btn img {
|
||||
.discussion .close-btn img{
|
||||
height: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.discussion p {
|
||||
.discussion p{
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding-left: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.discussion .participants {
|
||||
.discussion .participants{
|
||||
height: 200px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.discussion .participants .participant {
|
||||
.discussion .participants .participant{
|
||||
display: flex;
|
||||
margin: 5px 10px;
|
||||
background-color: #ffffff69;
|
||||
@ -869,11 +826,10 @@ div.modal-report-user {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.discussion .participants .participant:hover {
|
||||
.discussion .participants .participant:hover{
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.discussion .participants .participant:hover p {
|
||||
.discussion .participants .participant:hover p{
|
||||
color: black;
|
||||
}
|
||||
|
||||
@ -888,12 +844,12 @@ div.modal-report-user {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.discussion .participants .participant img {
|
||||
.discussion .participants .participant img{
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.discussion .participants .participant p {
|
||||
.discussion .participants .participant p{
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
margin-top: 2px;
|
||||
@ -913,11 +869,11 @@ div.modal-report-user {
|
||||
transition: all .5s ease;
|
||||
}
|
||||
|
||||
.discussion .participants .participant:hover button.report-btn {
|
||||
.discussion .participants .participant:hover button.report-btn{
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.discussion .messages {
|
||||
.discussion .messages{
|
||||
position: absolute;
|
||||
height: calc(100% - 390px);
|
||||
overflow-x: hidden;
|
||||
@ -926,28 +882,28 @@ div.modal-report-user {
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.discussion .messages h2 {
|
||||
.discussion .messages h2{
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.discussion .messages .message {
|
||||
.discussion .messages .message{
|
||||
margin: 5px 0px;
|
||||
float: right;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.discussion .messages .message.me {
|
||||
.discussion .messages .message.me{
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.discussion .messages .message p {
|
||||
.discussion .messages .message p{
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.discussion .messages .message p.body {
|
||||
.discussion .messages .message p.body{
|
||||
color: black;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
@ -957,8 +913,7 @@ div.modal-report-user {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.discussion .messages .message p a {
|
||||
.discussion .messages .message p a{
|
||||
color: white;
|
||||
}
|
||||
|
||||
@ -974,7 +929,7 @@ div.modal-report-user {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.discussion .send-message input {
|
||||
.discussion .send-message input{
|
||||
position: absolute;
|
||||
width: calc(100% - 20px);
|
||||
height: 30px;
|
||||
@ -985,22 +940,20 @@ div.modal-report-user {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.discussion .send-message img {
|
||||
.discussion .send-message img{
|
||||
position: absolute;
|
||||
margin-right: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #ffffff69;
|
||||
}
|
||||
|
||||
.discussion .send-message img:hover {
|
||||
.discussion .send-message img:hover{
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
|
||||
/** Action button **/
|
||||
|
||||
div.action {
|
||||
div.action{
|
||||
position: absolute;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
@ -1011,10 +964,9 @@ div.action {
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
div.action.info,
|
||||
div.action.warning,
|
||||
div.action.danger {
|
||||
div.action.danger{
|
||||
transition: all 1s ease;
|
||||
animation: mymove 1s;
|
||||
animation-iteration-count: infinite;
|
||||
@ -1028,18 +980,15 @@ div.action p.action-body{
|
||||
width: 350px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
div.action.warning p.action-body {
|
||||
div.action.warning p.action-body{
|
||||
background-color: #ff9800eb;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.action.danger p.action-body {
|
||||
div.action.danger p.action-body{
|
||||
background-color: #da0000e3;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.popUpElement {
|
||||
.popUpElement{
|
||||
font-family: 'Press Start 2P';
|
||||
text-align: left;
|
||||
color: white;
|
||||
@ -1064,8 +1013,10 @@ div.action.danger p.action-body {
|
||||
.popUpElement .buttonContainer {
|
||||
float: right;
|
||||
background-color: inherit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@keyframes mymove {
|
||||
0% {bottom: 40px;}
|
||||
50% {bottom: 30px;}
|
||||
@ -1115,9 +1066,6 @@ div.is-silent.hide {
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.emote-menu .emoji-picker .emoji-picker__emoji {
|
||||
font-family: "Twemoji Mozilla" !important;
|
||||
}
|
||||
|
||||
.popUpElement.overlay-text {
|
||||
|
||||
|
@ -7,7 +7,6 @@ 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 { POSTHOG_API_KEY, PROFILE_URL } from "./src/Enum/EnvironmentVariable";
|
||||
|
||||
const mode = process.env.NODE_ENV ?? "development";
|
||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||
@ -208,6 +207,8 @@ module.exports = {
|
||||
POSTHOG_API_KEY: null,
|
||||
POSTHOG_URL: null,
|
||||
NODE_ENV: mode,
|
||||
DISABLE_ANONYMOUS: false,
|
||||
OPID_LOGIN_SCREEN_PROVIDER: null,
|
||||
}),
|
||||
],
|
||||
} as Configuration & WebpackDevServer.Configuration;
|
||||
|
@ -1,16 +1,18 @@
|
||||
WA.onInit().then(() => {
|
||||
let message;
|
||||
|
||||
WA.room.onEnterZone("carpet", () => {
|
||||
WA.room.onEnterLayer("carpet").subscribe(() => {
|
||||
message = WA.ui.displayActionMessage({
|
||||
message: "This is a test message. Press space to display a chat message. Walk out to hide the message.",
|
||||
message:
|
||||
"This is a test message. Press space to display a chat message. Walk out to hide the message.",
|
||||
callback: () => {
|
||||
WA.chat.sendChatMessage("Hello world!", "The bot");
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
WA.room.onLeaveZone("carpet", () => {
|
||||
|
||||
WA.room.onLeaveLayer("carpet").subscribe(() => {
|
||||
message && message.remove();
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ import { PrometheusController } from "./Controller/PrometheusController";
|
||||
import { DebugController } from "./Controller/DebugController";
|
||||
import { App as uwsApp } from "./Server/sifrr.server";
|
||||
import { AdminController } from "./Controller/AdminController";
|
||||
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||
|
||||
class App {
|
||||
public app: uwsApp;
|
||||
@ -15,6 +16,7 @@ class App {
|
||||
public prometheusController: PrometheusController;
|
||||
private debugController: DebugController;
|
||||
private adminController: AdminController;
|
||||
private openIdProfileController: OpenIdProfileController;
|
||||
|
||||
constructor() {
|
||||
this.app = new uwsApp();
|
||||
@ -26,6 +28,7 @@ class App {
|
||||
this.prometheusController = new PrometheusController(this.app);
|
||||
this.debugController = new DebugController(this.app);
|
||||
this.adminController = new AdminController(this.app);
|
||||
this.openIdProfileController = new OpenIdProfileController(this.app);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { v4 } from "uuid";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"
|
||||
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
|
||||
export interface TokenInterface {
|
||||
userUuid: string;
|
||||
@ -56,19 +56,20 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { code, nonce, token } = parse(req.getQuery());
|
||||
const IPAddress = req.getHeader("x-forwarded-for");
|
||||
const { code, nonce, token, playUri } = 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) {
|
||||
if (authTokenData.accessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: token, username: authTokenData.username, userUuid : authTokenData.identifier }));
|
||||
return res.end(JSON.stringify({ ...resCheckTokenAuth, username: authTokenData.username, authToken: token }));
|
||||
} catch (err) {
|
||||
console.info("User was not connected", err);
|
||||
}
|
||||
@ -81,9 +82,14 @@ export class AuthenticateController extends BaseController {
|
||||
throw new Error("No sub in the response");
|
||||
}
|
||||
const authToken = jwtTokenManager.createAuthToken(sub, userInfo.access_token, userInfo.username);
|
||||
|
||||
//Get user data from Admin Back Office
|
||||
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||
const data = await this.getUserByUserIdentifier(sub, playUri as string, IPAddress);
|
||||
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: authToken, username: userInfo.username, userUuid : sub }));
|
||||
return res.end(JSON.stringify({ ...data, authToken, username: userInfo.username, userUuid : sub }));
|
||||
} catch (e) {
|
||||
console.error("openIDCallback => ERROR", e);
|
||||
return this.errorToResponse(e, res);
|
||||
@ -100,10 +106,10 @@ export class AuthenticateController extends BaseController {
|
||||
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
if (authTokenData.accessToken == undefined) {
|
||||
throw Error("Token cannot to be logout on Hydra");
|
||||
}
|
||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
||||
await openIDClient.logoutUser(authTokenData.accessToken);
|
||||
} catch (error) {
|
||||
console.error("openIDCallback => logout-callback", error);
|
||||
} finally {
|
||||
@ -202,20 +208,20 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { userIdentify, token } = parse(req.getQuery());
|
||||
const { 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) {
|
||||
if (authTokenData.accessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||
|
||||
//get login profile
|
||||
res.writeStatus("302");
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
@ -229,4 +235,26 @@ export class AuthenticateController extends BaseController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param email
|
||||
* @param playUri
|
||||
* @param IPAddress
|
||||
* @return FetchMemberDataByUuidResponse|object
|
||||
* @private
|
||||
*/
|
||||
private async getUserByUserIdentifier(
|
||||
email: string,
|
||||
playUri: string,
|
||||
IPAddress: string
|
||||
): Promise<FetchMemberDataByUuidResponse | object> {
|
||||
let data: FetchMemberDataByUuidResponse | object = {};
|
||||
try {
|
||||
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress);
|
||||
} catch (err) {
|
||||
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,9 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana
|
||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { SocketManager, socketManager } from "../Services/SocketManager";
|
||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_SOCKETS_TOKEN, ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||
import { Zone } from "_Model/Zone";
|
||||
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||
import { v4 } from "uuid";
|
||||
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
|
||||
|
||||
export class IoSocketController {
|
||||
@ -48,15 +47,19 @@ export class IoSocketController {
|
||||
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||
const token = query.token;
|
||||
if (token !== ADMIN_API_TOKEN) {
|
||||
console.log("Admin access refused for token: " + token);
|
||||
let authorizedRoomIds: string[];
|
||||
try {
|
||||
const data = jwtTokenManager.verifyAdminSocketToken(token as string);
|
||||
authorizedRoomIds = data.authorizedRoomIds;
|
||||
} catch (e) {
|
||||
console.error("Admin access refused for token: " + token);
|
||||
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
||||
return;
|
||||
}
|
||||
const roomId = query.roomId;
|
||||
if (typeof roomId !== "string") {
|
||||
console.error("Received");
|
||||
res.writeStatus("400 Bad Request").end("Missing room id");
|
||||
if (typeof roomId !== "string" || !authorizedRoomIds.includes(roomId)) {
|
||||
console.error("Invalid room id");
|
||||
res.writeStatus("403 Bad Request").end("Invalid room id");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -70,8 +73,6 @@ export class IoSocketController {
|
||||
},
|
||||
message: (ws, arrayBuffer, isBinary): void => {
|
||||
try {
|
||||
const roomId = ws.roomId as string;
|
||||
|
||||
//TODO refactor message type and data
|
||||
const message: { event: string; message: { type: string; message: unknown; userUuid: string } } =
|
||||
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
|
||||
@ -250,7 +251,7 @@ export class IoSocketController {
|
||||
roomId
|
||||
);
|
||||
console.error(e);
|
||||
throw new Error("User cannot access this room");
|
||||
throw new Error("User cannot access this world");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ export class MapController extends BaseController {
|
||||
return;
|
||||
}
|
||||
|
||||
const mapUrl = roomUrl.protocol + "//" + match[ 1 ];
|
||||
const mapUrl = roomUrl.protocol + "//" + match[1];
|
||||
|
||||
res.writeStatus("200 OK");
|
||||
this.addCorsHeaders(res);
|
||||
@ -65,7 +65,7 @@ export class MapController extends BaseController {
|
||||
tags: [],
|
||||
textures: [],
|
||||
contactPage: undefined,
|
||||
authenticationMandatory : DISABLE_ANONYMOUS,
|
||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||
} as MapDetailsData)
|
||||
);
|
||||
|
||||
@ -90,7 +90,7 @@ export class MapController extends BaseController {
|
||||
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
|
||||
|
||||
if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) {
|
||||
(mapDetails as MapDetailsData).authenticationMandatory = true;
|
||||
mapDetails.authenticationMandatory = true;
|
||||
}
|
||||
|
||||
res.writeStatus("200 OK");
|
||||
|
80
pusher/src/Controller/OpenIdProfileController.ts
Normal file
80
pusher/src/Controller/OpenIdProfileController.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { BaseController } from "./BaseController";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable";
|
||||
import { IntrospectionResponse } from "openid-client";
|
||||
|
||||
export class OpenIdProfileController extends BaseController {
|
||||
constructor(private App: TemplatedApp) {
|
||||
super();
|
||||
this.profileOpenId();
|
||||
}
|
||||
|
||||
profileOpenId() {
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/profile", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
|
||||
const { accessToken } = parse(req.getQuery());
|
||||
if (!accessToken) {
|
||||
throw Error("Access token expected cannot to be check on Hydra");
|
||||
}
|
||||
try {
|
||||
const resCheckTokenAuth = await openIDClient.checkTokenAuth(accessToken as string);
|
||||
if (!resCheckTokenAuth.email) {
|
||||
throw "Email was not found";
|
||||
}
|
||||
res.end(
|
||||
this.buildHtml(
|
||||
OPID_CLIENT_ISSUER,
|
||||
resCheckTokenAuth.email as string,
|
||||
resCheckTokenAuth.picture as string | undefined
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("profileCallback => ERROR", error);
|
||||
this.errorToResponse(error, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildHtml(domain: string, email: string, pictureUrl?: string) {
|
||||
return `
|
||||
<!DOCTYPE>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7, monospace;
|
||||
}
|
||||
body{
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
section{
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<section>
|
||||
<img src="${pictureUrl ? pictureUrl : "/images/profile"}">
|
||||
</section>
|
||||
<section>
|
||||
Profile validated by domain: <span style="font-weight: bold">${domain}</span>
|
||||
</section>
|
||||
<section>
|
||||
Your email: <span style="font-weight: bold">${email}</span>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ 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";
|
||||
export const ADMIN_SOCKETS_TOKEN = process.env.ADMIN_SOCKETS_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;
|
||||
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||
@ -12,11 +13,13 @@ const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || "8080") || 808
|
||||
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 120; // maximum time (in second) without activity before a socket is closed. Should be greater than 60 seconds in order to cope for Chrome intensive throttling (https://developer.chrome.com/blog/timer-throttling-in-chrome-88/#intensive-throttling)
|
||||
|
||||
export const FRONT_URL = process.env.FRONT_URL || "http://localhost";
|
||||
export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS ? process.env.DISABLE_ANONYMOUS == "true" : false;
|
||||
export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || "";
|
||||
export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
||||
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
|
||||
export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_URL || FRONT_URL + "/jwt";
|
||||
export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile";
|
||||
export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false;
|
||||
export const PUSHER_FORCE_ROOM_UPDATE = process.env.PUSHER_FORCE_ROOM_UPDATE ? process.env.PUSHER_FORCE_ROOM_UPDATE == "true" : false;
|
||||
export const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID || "";
|
||||
export const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET || "";
|
||||
export const OIDC_CLIENT_ISSUER = process.env.OIDC_CLIENT_ISSUER || "";
|
||||
|
||||
export {
|
||||
SECRET_KEY,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
@ -142,13 +142,19 @@ class AdminApi {
|
||||
});
|
||||
}
|
||||
|
||||
/*TODO add constant to use profile companny*/
|
||||
/**
|
||||
*
|
||||
* @param accessToken
|
||||
*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!ADMIN_URL) {
|
||||
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||
throw new Error("No admin backoffice set!");
|
||||
}
|
||||
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||
}
|
||||
|
||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
||||
async logoutOauth(token: string) {
|
||||
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||
import { uuid } from "uuidv4";
|
||||
import Jwt, { verify } from "jsonwebtoken";
|
||||
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||
@ -6,14 +6,21 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a sub (id) if logged in or an uuid if anonymous
|
||||
hydraAccessToken?: string;
|
||||
accessToken?: string;
|
||||
username?: string;
|
||||
}
|
||||
export interface AdminSocketTokenData {
|
||||
authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from.
|
||||
}
|
||||
export const tokenInvalidException = "tokenInvalid";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createAuthToken(identifier: string, hydraAccessToken?: string, username?: string) {
|
||||
return Jwt.sign({ identifier, hydraAccessToken, username }, SECRET_KEY, { expiresIn: "30d" });
|
||||
public verifyAdminSocketToken(token: string): AdminSocketTokenData {
|
||||
return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData;
|
||||
}
|
||||
|
||||
public createAuthToken(identifier: string, accessToken?: string, username?: string) {
|
||||
return Jwt.sign({ identifier, accessToken, username }, SECRET_KEY, { expiresIn: "30d" });
|
||||
}
|
||||
|
||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||
import { OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
import {
|
||||
OPID_CLIENT_ID,
|
||||
OPID_CLIENT_SECRET,
|
||||
OPID_CLIENT_ISSUER,
|
||||
OPID_CLIENT_REDIRECT_URL,
|
||||
} from "../Enum/EnvironmentVariable";
|
||||
|
||||
class OpenIDClient {
|
||||
private issuerPromise: Promise<Client> | null = null;
|
||||
|
||||
private initClient(): Promise<Client> {
|
||||
if (!this.issuerPromise) {
|
||||
this.issuerPromise = Issuer.discover(OIDC_CLIENT_ISSUER).then((issuer) => {
|
||||
this.issuerPromise = Issuer.discover(OPID_CLIENT_ISSUER).then((issuer) => {
|
||||
return new issuer.Client({
|
||||
client_id: OIDC_CLIENT_ID,
|
||||
client_secret: OIDC_CLIENT_SECRET,
|
||||
redirect_uris: [opidRedirectUri],
|
||||
client_id: OPID_CLIENT_ID,
|
||||
client_secret: OPID_CLIENT_SECRET,
|
||||
redirect_uris: [OPID_CLIENT_REDIRECT_URL],
|
||||
response_types: ["code"],
|
||||
});
|
||||
});
|
||||
@ -35,7 +38,7 @@ class OpenIDClient {
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string; username: string }> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.callback(OPID_CLIENT_REDIRECT_URL, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.userinfo(tokenSet).then((res) => {
|
||||
return {
|
||||
...res,
|
||||
|
@ -54,10 +54,10 @@ import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
const debug = Debug("socket");
|
||||
|
||||
interface AdminSocketRoomsList {
|
||||
[ index: string ]: number;
|
||||
[index: string]: number;
|
||||
}
|
||||
interface AdminSocketUsersList {
|
||||
[ index: string ]: boolean;
|
||||
[index: string]: boolean;
|
||||
}
|
||||
|
||||
export interface AdminSocketData {
|
||||
@ -635,7 +635,7 @@ export class SocketManager implements ZoneEventListener {
|
||||
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
||||
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
|
||||
} else {
|
||||
tabUrlRooms = [ clientRoomUrl ];
|
||||
tabUrlRooms = [clientRoomUrl];
|
||||
}
|
||||
|
||||
const roomMessage = new AdminRoomMessage();
|
||||
|
Loading…
Reference in New Issue
Block a user